agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v3 3/3] Adding the pg_stat_backend view 325+ messages / 2 participants [nested] [flat]
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v1 02/10] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of sequential scans initiated on tables and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 95 ++++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 7 ++ src/backend/utils/adt/pgstatfuncs.c | 55 ++++++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 4 ++ src/test/regress/expected/stats.out | 10 +++ src/test/regress/sql/stats.sql | 8 +++ 7 files changed, 188 insertions(+) 50.8% doc/src/sgml/ 26.9% src/backend/utils/adt/ 8.0% src/include/catalog/ 7.1% src/test/regress/expected/ 4.0% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..6868af55db4 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,19 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of sequential scans. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1185,72 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>seq_scan</structfield> <type>bigint</type> + </para> + <para> + The number of sequential scans initiated on tables. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5000,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..3ab7802cc5c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,13 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.seq_scan, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..2adfbdcb65c 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,61 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 3 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->heap_scan); + + if (backend_stats->stat_reset_timestamp != 0) + values[2] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[2] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..e87200374d8 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,timestamptz}', + proargmodes => '{i,o,o,o}', + proargnames => '{pid,pid,seq_scan,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..63f8f12b8a5 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,10 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + seq_scan, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, seq_scan, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..937bf4bfc5b 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -118,6 +118,8 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch, pg_catalog.pg_statio_user_tables AS b WHERE t.relname='tenk2' AND b.relname='tenk2'; COMMIT; +SELECT seq_scan AS seq_scan_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters CREATE TABLE trunc_stats_test(id serial); CREATE TABLE trunc_stats_test1(id serial, stuff text); @@ -219,6 +221,14 @@ SELECT st.seq_scan >= pr.seq_scan + 1, t | t | t | t (1 row) +SELECT seq_scan AS seq_scan_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :seq_scan_after > :seq_scan_before; + ?column? +---------- + t +(1 row) + SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages, st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1 FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..b743352eadb 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -38,6 +38,9 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch, WHERE t.relname='tenk2' AND b.relname='tenk2'; COMMIT; +SELECT seq_scan AS seq_scan_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + -- test effects of TRUNCATE on n_live_tup/n_dead_tup counters CREATE TABLE trunc_stats_test(id serial); CREATE TABLE trunc_stats_test1(id serial, stuff text); @@ -122,6 +125,11 @@ SELECT st.seq_scan >= pr.seq_scan + 1, FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr WHERE st.relname='tenk2' AND cl.relname='tenk2'; +SELECT seq_scan AS seq_scan_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :seq_scan_after > :seq_scan_before; + SELECT st.heap_blks_read + st.heap_blks_hit >= pr.heap_blks + cl.relpages, st.idx_blks_read + st.idx_blks_hit >= pr.idx_blks + 1 FROM pg_statio_user_tables AS st, pg_class AS cl, prevstats AS pr -- 2.34.1 --j6dXQ8GCuNzKPBpX Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0003-Adding-seq_tup_read-to-pg_stat_backend.patch" ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v3 3/3] Adding the pg_stat_backend view @ 2025-08-09 14:22 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 325+ messages in thread From: Bertrand Drouvot @ 2025-08-09 14:22 UTC (permalink / raw) This view displays one row per server process, showing statistics related to the current activity of that process. It currently displays the pid, the number of XIDs generated, the number of commits, the number of rollbacks and the time at which these statistics were last reset. It's built on top of a new function (pg_stat_get_backend_statistics()). The idea is the same as pg_stat_activity and pg_stat_get_activity(). Adding documentation and tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 115 +++++++++++++++++++++++++++ src/backend/catalog/system_views.sql | 9 +++ src/backend/utils/adt/pgstatfuncs.c | 57 +++++++++++++ src/include/catalog/pg_proc.dat | 9 +++ src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/stats.out | 17 ++++ src/test/regress/sql/stats.sql | 10 +++ 7 files changed, 223 insertions(+) 51.1% doc/src/sgml/ 3.2% src/backend/catalog/ 23.9% src/backend/utils/adt/ 7.2% src/include/catalog/ 9.4% src/test/regress/expected/ 4.9% src/test/regress/sql/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 3f4a27a736e..0146c812b40 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -320,6 +320,20 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry> + <structname>pg_stat_backend</structname> + <indexterm><primary>pg_stat_backend</primary></indexterm> + </entry> + <entry> + One row per server process, showing statistics related to + the current activity of that process, such as number of commits and + rollbacks. + See <link linkend="monitoring-pg-stat-backend-view"> + <structname>pg_stat_backend</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_replication</structname><indexterm><primary>pg_stat_replication</primary></indexterm></entry> <entry>One row per WAL sender process, showing statistics about @@ -1172,6 +1186,91 @@ description | Waiting for a newly initialized WAL file to reach durable storage </note> </sect2> + <sect2 id="monitoring-pg-stat-backend-view"> + <title><structname>pg_stat_backend</structname></title> + + <indexterm> + <primary>pg_stat_backend</primary> + </indexterm> + + <para> + The <structname>pg_stat_backend</structname> view will have one row + per server process, showing statistics related to + the current activity of that process. + </para> + + <table id="pg-stat-backend-view" xreflabel="pg_stat_backend"> + <title><structname>pg_stat_backend</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pid</structfield> <type>integer</type> + </para> + <para> + Process ID of this backend + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xid_count</structfield> <type>bigint</type> + </para> + <para> + The number of XID that have been generated by the backend. It does not take + into account virtual transaction ID on purpose. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_commit</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been committed. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>xact_rollback</structfield> <type>bigint</type> + </para> + <para> + The number of transactions that have been rolled back. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>stats_reset</structfield> <type>timestamp with time zone</type> + </para> + <para> + Time at which these statistics were last reset + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <note> + <para> + The view does not return statistics for the checkpointer, + the background writer, the startup process and the autovacuum launcher. + </para> + </note> + </sect2> + <sect2 id="monitoring-pg-stat-replication-view"> <title><structname>pg_stat_replication</structname></title> @@ -4921,6 +5020,22 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>pg_stat_get_backend_statistics</primary> + </indexterm> + <function>pg_stat_get_backend_statistics</function> ( <type>integer</type> ) + <returnvalue>setof record</returnvalue> + </para> + <para> + Returns a record of statistics bout the backend with the specified + process ID, or one record for each active backend in the system + if <literal>NULL</literal> is specified. The fields returned are a + subset of those in the <structname>pg_stat_backend</structname> view. + </para></entry> + </row> + <row> <entry id="pg-stat-get-backend-wal" role="func_table_entry"><para role="func_signature"> <indexterm> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 1b3c5a55882..96a1a556773 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -911,6 +911,15 @@ CREATE VIEW pg_stat_activity AS LEFT JOIN pg_database AS D ON (S.datid = D.oid) LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid); +CREATE VIEW pg_stat_backend AS + SELECT + S.pid, + S.xid_count, + S.xact_commit, + S.xact_rollback, + S.stats_reset + FROM pg_stat_get_backend_statistics(NULL) AS S; + CREATE VIEW pg_stat_replication AS SELECT S.pid, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index c756c2bebaa..dcb20009bec 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -685,6 +685,63 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Returns statistics of PG backends. + */ +Datum +pg_stat_get_backend_statistics(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_BACKEND_STATS_COLS 5 + int num_backends = pgstat_fetch_stat_numbackends(); + int curr_backend; + int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + InitMaterializedSRF(fcinfo, 0); + + /* 1-based index */ + for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) + { + /* for each row */ + Datum values[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_BACKEND_STATS_COLS] = {0}; + LocalPgBackendStatus *local_beentry; + PgBackendStatus *beentry; + PgStat_Backend *backend_stats; + + /* Get the next one in the list */ + local_beentry = pgstat_get_local_beentry_by_index(curr_backend); + beentry = &local_beentry->backendStatus; + + /* If looking for specific PID, ignore all the others */ + if (pid != -1 && beentry->st_procpid != pid) + continue; + + backend_stats = pgstat_fetch_stat_backend_by_pid(beentry->st_procpid, NULL); + + values[0] = Int32GetDatum(beentry->st_procpid); + + if (!backend_stats) + continue; + + values[1] = Int64GetDatum(backend_stats->xid_count); + values[2] = Int64GetDatum(backend_stats->xact_commit); + values[3] = Int64GetDatum(backend_stats->xact_rollback); + + if (backend_stats->stat_reset_timestamp != 0) + values[4] = TimestampTzGetDatum(backend_stats->stat_reset_timestamp); + else + nulls[4] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* If only a single backend was requested, and we found it, break. */ + if (pid != -1) + break; + } + + return (Datum) 0; +} Datum pg_backend_pid(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 118d6da1ace..dc0c0db6358 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5641,6 +5641,15 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}', prosrc => 'pg_stat_get_activity' }, +{ oid => '9555', + descr => 'statistics: statistics about currently active backends', + proname => 'pg_stat_get_backend_statistics', prorows => '100', proisstrict => 'f', + proretset => 't', provolatile => 's', proparallel => 'r', + prorettype => 'record', proargtypes => 'int4', + proallargtypes => '{int4,int4,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{pid,pid,xid_count,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_backend_statistics' }, { oid => '6318', descr => 'describe wait events', proname => 'pg_get_wait_events', procost => '10', prorows => '250', proretset => 't', provolatile => 'v', prorettype => 'record', diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 35e8aad7701..a107f6ca44d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1847,6 +1847,12 @@ pg_stat_archiver| SELECT archived_count, last_failed_time, stats_reset FROM pg_stat_get_archiver() s(archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, stats_reset); +pg_stat_backend| SELECT pid, + xid_count, + xact_commit, + xact_rollback, + stats_reset + FROM pg_stat_get_backend_statistics(NULL::integer) s(pid, xid_count, xact_commit, xact_rollback, stats_reset); pg_stat_bgwriter| SELECT pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_alloc() AS buffers_alloc, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 605f5070376..a4b6d27c31d 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -135,11 +135,28 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset +SELECT :xact_commit_after > :xact_commit_before; + ?column? +---------- + t +(1 row) + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 54e72866344..f25368d1d25 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -58,12 +58,22 @@ INSERT INTO trunc_stats_test1 DEFAULT VALUES; UPDATE trunc_stats_test1 SET id = id + 10 WHERE id IN (1, 2); DELETE FROM trunc_stats_test1 WHERE id = 3; +-- in passing, check that backend's commit is incrementing +SELECT xact_commit AS xact_commit_before + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + BEGIN; UPDATE trunc_stats_test1 SET id = id + 100; TRUNCATE trunc_stats_test1; INSERT INTO trunc_stats_test1 DEFAULT VALUES; COMMIT; +SELECT pg_stat_force_next_flush(); +SELECT xact_commit AS xact_commit_after + FROM pg_stat_backend WHERE pid = pg_backend_pid() \gset + +SELECT :xact_commit_after > :xact_commit_before; + -- use a savepoint: 1 insert, 1 live BEGIN; INSERT INTO trunc_stats_test2 DEFAULT VALUES; -- 2.34.1 --u6+Li7FU79Mt/lVd-- ^ permalink raw reply [nested|flat] 325+ messages in thread
* [PATCH v51 08/10] Introduce an option to make logical replication database specific. @ 2026-04-03 10:34 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 325+ messages in thread From: Antonin Houska @ 2026-04-03 10:34 UTC (permalink / raw) By default, the logical decoding assumes access to shared catalogs, so the snapshot builder needs to consider cluster-wide XIDs during startup. That in turn means that, if any transaction is already running (and has XID assigned), the snapshot builder needs to wait for its completion, as it does not know if that transaction performed catalog changes earlier. Possible problem with this concept is that if REPACK (CONCURRENTLY) is running in some database, backends running the same command in other databases get stuck until the first one has committed. Thus only as single backend in the cluster can run REPACK (CONCURRENTLY) at any time. Likewise, REPACK (CONCURRENTLY) can block walsenders starting on behalf of subscriptions throughout the cluster. This patch introduces the possibility for a backend to declare that its output plugin does not use shared catalogs (i.e. catalogs that can be changed by transactions running in other databases in the cluster). In that case, no snapshot the backend will use during the decoding needs to contain information about transactions running in other databases. Thus the snapshot builder only needs to wait for completion of transactions in the current database. Currently we only use this option in the REPACK background worker. It could possibly be used in walsender involved in logical replication too, however that would need thorough analysis of its output plugin. The patch bumps WAL version number, due to a new field in xl_running_xacts. --- contrib/pg_visibility/pg_visibility.c | 4 ++-- src/backend/access/index/genam.c | 18 ++++++++++++++++ src/backend/access/rmgrdesc/standbydesc.c | 2 ++ src/backend/access/transam/xlog.c | 2 +- src/backend/access/transam/xlogfuncs.c | 2 +- src/backend/commands/repack_worker.c | 8 +++++++ src/backend/postmaster/bgwriter.c | 2 +- src/backend/replication/logical/snapbuild.c | 10 ++++++++- src/backend/replication/slot.c | 12 +++++++++-- src/backend/storage/ipc/procarray.c | 23 ++++++++++++++++++++- src/backend/storage/ipc/standby.c | 14 +++++++++++-- src/include/access/genam.h | 8 +++++++ src/include/access/xlog_internal.h | 2 +- src/include/storage/procarray.h | 2 +- src/include/storage/standby.h | 3 ++- src/include/storage/standbydefs.h | 1 + 16 files changed, 99 insertions(+), 14 deletions(-) diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c index dfab0b64cf5..d564bd2a00c 100644 --- a/contrib/pg_visibility/pg_visibility.c +++ b/contrib/pg_visibility/pg_visibility.c @@ -621,7 +621,7 @@ GetStrictOldestNonRemovableTransactionId(Relation rel) else if (rel == NULL || rel->rd_rel->relisshared) { /* Shared relation: take into account all running xids */ - runningTransactions = GetRunningTransactionData(); + runningTransactions = GetRunningTransactionData(InvalidOid); LWLockRelease(ProcArrayLock); LWLockRelease(XidGenLock); return runningTransactions->oldestRunningXid; @@ -632,7 +632,7 @@ GetStrictOldestNonRemovableTransactionId(Relation rel) * Normal relation: take into account xids running within the current * database */ - runningTransactions = GetRunningTransactionData(); + runningTransactions = GetRunningTransactionData(InvalidOid); LWLockRelease(ProcArrayLock); LWLockRelease(XidGenLock); return runningTransactions->oldestDatabaseRunningXid; diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 1408989c568..df092dc999a 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -37,6 +37,14 @@ #include "utils/ruleutils.h" #include "utils/snapmgr.h" +/* + * If a backend is going to do logical decoding and if the output plugin does + * not need to access shared catalogs, setting this variable to false can make + * the decoding startup faster. In particular, the backend will not need to + * wait for completion of already running transactions in other databases. + */ +bool accessSharedCatalogsInDecoding = true; + /* ---------------------------------------------------------------- * general access method routines @@ -394,6 +402,16 @@ systable_beginscan(Relation heapRelation, SysScanDesc sysscan; Relation irel; + /* + * If this backend promised that it won't access shared catalogs during + * logical decoding, this seems to be the right place to check. + * + * XXX Should this be ereport(ERROR) ? + */ + Assert(!HistoricSnapshotActive() || + accessSharedCatalogsInDecoding || + !heapRelation->rd_rel->relisshared); + if (indexOK && !IgnoreSystemIndexes && !ReindexIsProcessingIndex(indexId)) diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c index 0a291354ae2..685d1bdb024 100644 --- a/src/backend/access/rmgrdesc/standbydesc.c +++ b/src/backend/access/rmgrdesc/standbydesc.c @@ -41,6 +41,8 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec) for (i = 0; i < xlrec->subxcnt; i++) appendStringInfo(buf, " %u", xlrec->xids[xlrec->xcnt + i]); } + + appendStringInfo(buf, "; dbid: %u", xlrec->dbid); } void diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 2c1c6f88b74..8da8eff93e4 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7336,7 +7336,7 @@ CreateCheckPoint(int flags) * recovery we don't need to write running xact data. */ if (!shutdown && XLogStandbyInfoActive()) - LogStandbySnapshot(); + LogStandbySnapshot(InvalidOid); START_CRIT_SECTION(); diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 65bbaeda59c..0f5979691e6 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -245,7 +245,7 @@ pg_log_standby_snapshot(PG_FUNCTION_ARGS) (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pg_log_standby_snapshot() can only be used if \"wal_level\" >= \"replica\""))); - recptr = LogStandbySnapshot(); + recptr = LogStandbySnapshot(InvalidOid); /* * As a convenience, return the WAL location of the last inserted record diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index c85166ba849..ff34e246469 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -16,6 +16,7 @@ */ #include "postgres.h" +#include "access/genam.h" #include "access/table.h" #include "access/xlog_internal.h" #include "access/xlogutils.h" @@ -237,6 +238,13 @@ repack_setup_logical_decoding(Oid relid) EnsureLogicalDecodingEnabled(); + /* + * By declaring that our output plugin does not need shared catalogs, we + * avoid waiting for completion of transactions running in other databases + * than the one we're connected to. + */ + accessSharedCatalogsInDecoding = false; + /* * Neither prepare_write nor do_write callback nor update_progress is * useful for us. diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index 1d8947774a9..a30de4262eb 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -289,7 +289,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len) if (now >= timeout && last_snapshot_lsn <= GetLastImportantRecPtr()) { - last_snapshot_lsn = LogStandbySnapshot(); + last_snapshot_lsn = LogStandbySnapshot(InvalidOid); last_snapshot_ts = now; } } diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index b4269a3b102..2e3926e1d13 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -125,6 +125,7 @@ #include <sys/stat.h> #include <unistd.h> +#include "access/genam.h" #include "access/heapam_xlog.h" #include "access/transam.h" #include "access/xact.h" @@ -1465,7 +1466,14 @@ SnapBuildWaitSnapshot(xl_running_xacts *running, TransactionId cutoff) */ if (!RecoveryInProgress()) { - LogStandbySnapshot(); + Oid dbid; + + /* + * Only consider transactions of the current database if our plugin is + * not supposed to access shared catalogs. + */ + dbid = accessSharedCatalogsInDecoding ? InvalidOid : MyDatabaseId; + LogStandbySnapshot(dbid); } } diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index a9092fc2382..d553bd5dbff 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -39,6 +39,7 @@ #include <unistd.h> #include <sys/stat.h> +#include "access/genam.h" #include "access/transam.h" #include "access/xlog_internal.h" #include "access/xlogrecovery.h" @@ -1760,9 +1761,16 @@ ReplicationSlotReserveWal(void) if (!RecoveryInProgress() && SlotIsLogical(slot)) { XLogRecPtr flushptr; + Oid dbid; - /* make sure we have enough information to start */ - flushptr = LogStandbySnapshot(); + /* + * Make sure we have enough information to start. + * + * Only consider transactions of the current database if our plugin is + * not supposed to access shared catalogs. + */ + dbid = accessSharedCatalogsInDecoding ? InvalidOid : MyDatabaseId; + flushptr = LogStandbySnapshot(dbid); /* and make sure it's fsynced to disk */ XLogFlush(flushptr); diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index cc207cb56e3..92ed34128e5 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -2631,9 +2631,11 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc) * * Note that if any transaction has overflowed its cached subtransactions * then there is no real need include any subtransactions. + * + * If 'dbid' is valid, only gather transactions running in that database. */ RunningTransactions -GetRunningTransactionData(void) +GetRunningTransactionData(Oid dbid) { /* result workspace */ static RunningTransactionsData CurrentRunningXactsData; @@ -2708,6 +2710,18 @@ GetRunningTransactionData(void) if (!TransactionIdIsValid(xid)) continue; + /* + * Filter by database OID if requested. + */ + if (OidIsValid(dbid)) + { + int pgprocno = arrayP->pgprocnos[index]; + PGPROC *proc = &allProcs[pgprocno]; + + if (proc->databaseId != dbid) + continue; + } + /* * Be careful not to exclude any xids before calculating the values of * oldestRunningXid and suboverflowed, since these are used to clean @@ -2758,6 +2772,12 @@ GetRunningTransactionData(void) PGPROC *proc = &allProcs[pgprocno]; int nsubxids; + /* + * Filter by database OID if requested. + */ + if (OidIsValid(dbid) && proc->databaseId != dbid) + continue; + /* * Save subtransaction XIDs. Other backends can't add or remove * entries while we're holding XidGenLock. @@ -2791,6 +2811,7 @@ GetRunningTransactionData(void) * increases if slots do. */ + CurrentRunningXacts->dbid = dbid; CurrentRunningXacts->xcnt = count - subcount; CurrentRunningXacts->subxcnt = subcount; CurrentRunningXacts->subxid_status = suboverflowed ? SUBXIDS_IN_SUBTRANS : SUBXIDS_IN_ARRAY; diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index de9092fdf5b..c653ea742bc 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -1188,6 +1188,14 @@ standby_redo(XLogReaderState *record) xl_running_xacts *xlrec = (xl_running_xacts *) XLogRecGetData(record); RunningTransactionsData running; + /* + * Records issued for specific database are not suitable for physical + * replication because that affects the whole cluster. In particular, + * the list of XID is probably incomplete here. + */ + if (OidIsValid(xlrec->dbid)) + return; + running.xcnt = xlrec->xcnt; running.subxcnt = xlrec->subxcnt; running.subxid_status = xlrec->subxid_overflow ? SUBXIDS_MISSING : SUBXIDS_IN_ARRAY; @@ -1277,11 +1285,12 @@ standby_redo(XLogReaderState *record) * as there's no independent knob to just enable logical decoding. For * details of how this is used, check snapbuild.c's introductory comment. * + * If 'dbid' is valid, only gather transactions running in that database. * * Returns the RecPtr of the last inserted record. */ XLogRecPtr -LogStandbySnapshot(void) +LogStandbySnapshot(Oid dbid) { XLogRecPtr recptr; RunningTransactions running; @@ -1314,7 +1323,7 @@ LogStandbySnapshot(void) * Log details of all in-progress transactions. This should be the last * record we write, because standby will open up when it sees this. */ - running = GetRunningTransactionData(); + running = GetRunningTransactionData(dbid); /* * GetRunningTransactionData() acquired ProcArrayLock, we must release it. @@ -1358,6 +1367,7 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts) xl_running_xacts xlrec; XLogRecPtr recptr; + xlrec.dbid = CurrRunningXacts->dbid; xlrec.xcnt = CurrRunningXacts->xcnt; xlrec.subxcnt = CurrRunningXacts->subxcnt; xlrec.subxid_overflow = (CurrRunningXacts->subxid_status != SUBXIDS_IN_ARRAY); diff --git a/src/include/access/genam.h b/src/include/access/genam.h index b69320a7fc8..56c573399ab 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -136,6 +136,14 @@ typedef struct IndexOrderByDistance bool isnull; } IndexOrderByDistance; +/* + * Is the backend interested in shared catalogs when performing logical + * decoding? + * + * XXX Is there a better place for this declaration? + */ +extern bool accessSharedCatalogsInDecoding; + /* * generalized index_ interface routines (in indexam.c) */ diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 755835d63bf..ae19982d88d 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -31,7 +31,7 @@ /* * Each page of XLOG file has a header like this: */ -#define XLOG_PAGE_MAGIC 0xD11E /* can be used as WAL version indicator */ +#define XLOG_PAGE_MAGIC 0xD11F /* can be used as WAL version indicator */ typedef struct XLogPageHeaderData { diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index abdf021e66e..377b3060b9f 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -49,7 +49,7 @@ extern bool ProcArrayInstallImportedXmin(TransactionId xmin, VirtualTransactionId *sourcevxid); extern bool ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc); -extern RunningTransactions GetRunningTransactionData(void); +extern RunningTransactions GetRunningTransactionData(Oid dbid); extern bool TransactionIdIsInProgress(TransactionId xid); extern TransactionId GetOldestNonRemovableTransactionId(Relation rel); diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h index 6a314c693cd..8715c08e94f 100644 --- a/src/include/storage/standby.h +++ b/src/include/storage/standby.h @@ -126,6 +126,7 @@ typedef enum typedef struct RunningTransactionsData { + Oid dbid; /* only track xacts in this database */ int xcnt; /* # of xact ids in xids[] */ int subxcnt; /* # of subxact ids in xids[] */ subxids_array_status subxid_status; @@ -143,7 +144,7 @@ typedef RunningTransactionsData *RunningTransactions; extern void LogAccessExclusiveLock(Oid dbOid, Oid relOid); extern void LogAccessExclusiveLockPrepare(void); -extern XLogRecPtr LogStandbySnapshot(void); +extern XLogRecPtr LogStandbySnapshot(Oid dbid); extern void LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs, bool relcacheInitFileInval); diff --git a/src/include/storage/standbydefs.h b/src/include/storage/standbydefs.h index 231d251fd51..e75b7078766 100644 --- a/src/include/storage/standbydefs.h +++ b/src/include/storage/standbydefs.h @@ -46,6 +46,7 @@ typedef struct xl_standby_locks */ typedef struct xl_running_xacts { + Oid dbid; /* only track xacts in this database */ int xcnt; /* # of xact ids in xids[] */ int subxcnt; /* # of subxact ids in xids[] */ bool subxid_overflow; /* snapshot overflowed, subxids missing */ -- 2.47.3 --r2slln3zpmilwu22 Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v51-0009-Reserve-replication-slots-specifically-for-REPAC.patch" ^ permalink raw reply [nested|flat] 325+ messages in thread
end of thread, other threads:[~2026-04-03 10:34 UTC | newest] Thread overview: 325+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v1 02/10] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2025-08-09 14:22 [PATCH v3 3/3] Adding the pg_stat_backend view Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-04-03 10:34 [PATCH v51 08/10] Introduce an option to make logical replication database specific. Antonin Houska <ah@cybertec.at>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox