diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 858788b227c4e..1800c51bb700e 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -547,6 +547,15 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser + + pg_stat_rolepg_stat_role + One row per role, showing statistics about sessions and + transactions of that role. See + + pg_stat_role for details. + + + pg_stat_slrupg_stat_slru One row per SLRU, showing statistics of operations. See @@ -2578,6 +2587,99 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + <structname>pg_stat_role</structname> + + + pg_stat_role + + + + The pg_stat_role view will contain one row per + role, showing cumulative statistics about the activity of sessions that + logged in as that role. Activity is attributed to the login role of a + session; SET ROLE and SECURITY + DEFINER functions do not change the role activity is counted + for. Only client sessions are counted, not background processes such as + autovacuum workers. + + + + <structname>pg_stat_role</structname> View + + + + + Column Type + + + Description + + + + + + + + roleid oid + + + OID of the role + + + + + + rolname name + + + Name of the role + + + + + + sessions bigint + + + Number of sessions established by this role + + + + + + xact_commit bigint + + + Number of transactions in sessions of this role that have been + committed + + + + + + xact_rollback bigint + + + Number of transactions in sessions of this role that have been + rolled back + + + + + + stats_reset timestamp with time zone + + + Time at which these statistics were last reset + + + + +
+ +
+ <structname>pg_stat_ssl</structname> @@ -5984,6 +6086,26 @@ description | Waiting for a newly initialized WAL file to reach durable storage can be granted EXECUTE to run the function.
+ + + + + pg_stat_reset_role_stats + + pg_stat_reset_role_stats ( oid ) + void + + + Resets statistics for a single role shown in the + pg_stat_role view to zero. If + the argument is NULL, reset statistics for all + roles. + + + This function is restricted to superusers by default, but other users + can be granted EXECUTE to run the function. + + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 6c1c5545cb56a..bb1e3f5b0a0a3 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1561,6 +1561,17 @@ CREATE VIEW pg_stat_subscription_stats AS FROM pg_subscription as s, pg_stat_get_subscription_stats(s.oid) as ss; +CREATE VIEW pg_stat_role AS + SELECT + rs.roleid, + r.rolname, + rs.sessions, + rs.xact_commit, + rs.xact_rollback, + rs.stats_reset + FROM pg_roles as r, + pg_stat_get_role_stats(r.oid) as rs; + CREATE VIEW pg_wait_events AS SELECT * FROM pg_get_wait_events(); diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index be11c49f919d0..f27bd2d6a1310 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -32,6 +32,7 @@ #include "commands/user.h" #include "libpq/crypt.h" #include "miscadmin.h" +#include "pgstat.h" #include "port/pg_bitutils.h" #include "storage/lmgr.h" #include "utils/acl.h" @@ -606,6 +607,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) /* Post creation hook for new role */ InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0); + /* Create the statistics entry for the new role */ + pgstat_create_role(roleid); + /* * Close pg_authid, but keep lock till commit. */ @@ -1325,6 +1329,9 @@ DropRole(DropRoleStmt *stmt) * Remove settings for this role. */ DropSetting(InvalidOid, roleid); + + /* Drop the statistics entry for this role */ + pgstat_drop_role(roleid); } /* diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index b6bdfe213feec..e1265a91709b9 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4497,6 +4497,7 @@ PostgresMain(const char *dbname, const char *username) on_proc_exit(log_disconnections, 0); pgstat_report_connect(MyDatabaseId); + pgstat_report_role_connect(GetSessionUserId()); /* Perform initialization specific to a WAL sender process. */ if (am_walsender) diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile index 5fed953c28a7f..9caa719892527 100644 --- a/src/backend/utils/activity/Makefile +++ b/src/backend/utils/activity/Makefile @@ -30,6 +30,7 @@ OBJS = \ pgstat_lock.o \ pgstat_relation.o \ pgstat_replslot.o \ + pgstat_role.o \ pgstat_shmem.o \ pgstat_slru.o \ pgstat_subscription.o \ diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build index 470b5dac402bd..74cfb9b266efe 100644 --- a/src/backend/utils/activity/meson.build +++ b/src/backend/utils/activity/meson.build @@ -15,6 +15,7 @@ backend_sources += files( 'pgstat_lock.c', 'pgstat_relation.c', 'pgstat_replslot.c', + 'pgstat_role.c', 'pgstat_shmem.c', 'pgstat_slru.c', 'pgstat_subscription.c', diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 8b2a5ec367503..74c02d2b9dc34 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -382,6 +382,23 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE] .reset_timestamp_cb = pgstat_backend_reset_timestamp_cb, }, + [PGSTAT_KIND_ROLE] = { + .name = "role", + + .fixed_amount = false, + .write_to_file = true, + /* so pg_stat_role entries can be seen in all databases */ + .accessed_across_databases = true, + + .shared_size = sizeof(PgStatShared_Role), + .shared_data_off = offsetof(PgStatShared_Role, stats), + .shared_data_len = sizeof(((PgStatShared_Role *) 0)->stats), + .pending_size = sizeof(PgStat_StatRoleEntry), + + .flush_pending_cb = pgstat_role_flush_cb, + .reset_timestamp_cb = pgstat_role_reset_timestamp_cb, + }, + /* stats for fixed-numbered (mostly 1) objects */ [PGSTAT_KIND_ARCHIVER] = { @@ -786,6 +803,7 @@ pgstat_report_stat(bool force) } pgstat_update_dbstats(now); + pgstat_update_role_stats(); /* don't wait for lock acquisition when !force */ nowait = !force; diff --git a/src/backend/utils/activity/pgstat_role.c b/src/backend/utils/activity/pgstat_role.c new file mode 100644 index 0000000000000..7022453629aa6 --- /dev/null +++ b/src/backend/utils/activity/pgstat_role.c @@ -0,0 +1,200 @@ +/* ------------------------------------------------------------------------- + * + * pgstat_role.c + * Implementation of role statistics. + * + * This file contains the implementation of role statistics. It is kept + * separate from pgstat.c to enforce the line between the statistics access / + * storage implementation and the details about individual types of + * statistics. + * + * Statistics are attributed to the login role of a session (the one + * reported by pgstat_report_role_connect()), not to the current user, so + * SET ROLE and SECURITY DEFINER functions do not redirect where activity + * is counted. + * + * Copyright (c) 2001-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/activity/pgstat_role.c + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" +#include "utils/pgstat_internal.h" + + +/* + * The login role of this session, as reported by + * pgstat_report_role_connect(). Remains InvalidOid in processes that don't + * report role statistics (e.g. autovacuum workers, background workers). + */ +static Oid pgStatSessionRoleId = InvalidOid; + +/* + * Transaction counts not yet transferred to the pending entry. Like the + * database transaction counters, these are accumulated at transaction end + * and moved to the pending entry by pgstat_update_role_stats(). + */ +static PgStat_Counter pgStatRoleXactCommit = 0; +static PgStat_Counter pgStatRoleXactRollback = 0; + +static PgStat_StatRoleEntry *pgstat_prep_role_pending(Oid roleid); + + +/* + * Report creating the role. + */ +void +pgstat_create_role(Oid roleid) +{ + /* Ensures that stats are dropped if transaction rolls back */ + pgstat_create_transactional(PGSTAT_KIND_ROLE, + InvalidOid, roleid); + + /* Create and initialize the role stats entry */ + pgstat_get_entry_ref(PGSTAT_KIND_ROLE, InvalidOid, roleid, + true, NULL); + pgstat_reset_entry(PGSTAT_KIND_ROLE, InvalidOid, roleid, 0); +} + +/* + * Report dropping the role. + * + * Ensures that stats are dropped if transaction commits. + */ +void +pgstat_drop_role(Oid roleid) +{ + pgstat_drop_transactional(PGSTAT_KIND_ROLE, + InvalidOid, roleid); +} + +/* + * Notify stats system of a new connection, attributed to the given login + * role. + * + * Like pgstat_report_connect(), only count client sessions, not background + * processes. + */ +void +pgstat_report_role_connect(Oid roleid) +{ + PgStat_StatRoleEntry *pending; + + if (MyBackendType != B_BACKEND) + return; + + /* remember the login role for transaction accounting */ + pgStatSessionRoleId = roleid; + + pending = pgstat_prep_role_pending(roleid); + pending->sessions++; +} + +/* + * Called from access/xact.c at transaction commit/abort. + */ +void +AtEOXact_PgStat_Role(bool isCommit, bool parallel) +{ + /* Don't count parallel worker transaction stats */ + if (parallel) + return; + + /* Don't count transactions in sessions not tied to a login role */ + if (!OidIsValid(pgStatSessionRoleId)) + return; + + if (isCommit) + pgStatRoleXactCommit++; + else + pgStatRoleXactRollback++; +} + +/* + * Subroutine for pgstat_report_stat(): Handle xact commit/rollback counts + * for the session's login role. + */ +void +pgstat_update_role_stats(void) +{ + PgStat_StatRoleEntry *pending; + + if (!OidIsValid(pgStatSessionRoleId)) + return; + + if (pgStatRoleXactCommit == 0 && pgStatRoleXactRollback == 0) + return; + + pending = pgstat_prep_role_pending(pgStatSessionRoleId); + pending->xact_commit += pgStatRoleXactCommit; + pending->xact_rollback += pgStatRoleXactRollback; + + pgStatRoleXactCommit = 0; + pgStatRoleXactRollback = 0; +} + +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one role or NULL. NULL doesn't mean + * that the role doesn't exist, just that there are no statistics, so the + * caller is better off to report ZERO instead. + */ +PgStat_StatRoleEntry * +pgstat_fetch_stat_role_entry(Oid roleid) +{ + return (PgStat_StatRoleEntry *) + pgstat_fetch_entry(PGSTAT_KIND_ROLE, InvalidOid, roleid, NULL); +} + +/* + * Find or create a local PgStat_StatRoleEntry entry for roleid. + */ +static PgStat_StatRoleEntry * +pgstat_prep_role_pending(Oid roleid) +{ + PgStat_EntryRef *entry_ref; + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_ROLE, InvalidOid, + roleid, NULL); + + return entry_ref->pending; +} + +/* + * Flush out pending stats for the entry + * + * If nowait is true and the lock could not be immediately acquired, returns + * false without flushing the entry. Otherwise returns true. + */ +bool +pgstat_role_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + PgStat_StatRoleEntry *localent; + PgStatShared_Role *sharedent; + + localent = (PgStat_StatRoleEntry *) entry_ref->pending; + sharedent = (PgStatShared_Role *) entry_ref->shared_stats; + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + +#define PGSTAT_ACCUM_ROLECOUNT(item) \ + (sharedent)->stats.item += (localent)->item + PGSTAT_ACCUM_ROLECOUNT(sessions); + PGSTAT_ACCUM_ROLECOUNT(xact_commit); + PGSTAT_ACCUM_ROLECOUNT(xact_rollback); +#undef PGSTAT_ACCUM_ROLECOUNT + + pgstat_unlock_entry(entry_ref); + return true; +} + +void +pgstat_role_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts) +{ + ((PgStatShared_Role *) header)->stats.stat_reset_timestamp = ts; +} diff --git a/src/backend/utils/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c index 3e1978775e163..b45a47b51ca69 100644 --- a/src/backend/utils/activity/pgstat_xact.c +++ b/src/backend/utils/activity/pgstat_xact.c @@ -42,6 +42,7 @@ AtEOXact_PgStat(bool isCommit, bool parallel) PgStat_SubXactStatus *xact_state; AtEOXact_PgStat_Database(isCommit, parallel); + AtEOXact_PgStat_Role(isCommit, parallel); /* handle transactional stats information */ xact_state = pgStatXactStack; diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 565d0e70768bb..808debb18e48e 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -2151,6 +2151,31 @@ pg_stat_reset_subscription_stats(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* Reset role stats (a specific one or all of them) */ +Datum +pg_stat_reset_role_stats(PG_FUNCTION_ARGS) +{ + Oid roleid; + + if (PG_ARGISNULL(0)) + { + /* Clear all role stats */ + pgstat_reset_of_kind(PGSTAT_KIND_ROLE); + } + else + { + roleid = PG_GETARG_OID(0); + + if (!OidIsValid(roleid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid role OID %u", roleid))); + pgstat_reset(PGSTAT_KIND_ROLE, InvalidOid, roleid); + } + + PG_RETURN_VOID(); +} + Datum pg_stat_get_archiver(PG_FUNCTION_ARGS) { @@ -2384,6 +2409,71 @@ pg_stat_get_subscription_stats(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } +/* + * Get the statistics for the given role. If the role statistics are not + * available, return all-zeros stats. + */ +Datum +pg_stat_get_role_stats(PG_FUNCTION_ARGS) +{ +#define PG_STAT_GET_ROLE_STATS_COLS 5 + Oid roleid = PG_GETARG_OID(0); + TupleDesc tupdesc; + Datum values[PG_STAT_GET_ROLE_STATS_COLS] = {0}; + bool nulls[PG_STAT_GET_ROLE_STATS_COLS] = {0}; + PgStat_StatRoleEntry *roleentry; + PgStat_StatRoleEntry allzero; + int i = 0; + + /* Get role stats */ + roleentry = pgstat_fetch_stat_role_entry(roleid); + + /* Initialise attributes information in the tuple descriptor */ + tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_ROLE_STATS_COLS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "roleid", + OIDOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "sessions", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "xact_commit", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "xact_rollback", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stats_reset", + TIMESTAMPTZOID, -1, 0); + TupleDescFinalize(tupdesc); + BlessTupleDesc(tupdesc); + + if (!roleentry) + { + /* If the role is not found, initialise its stats */ + memset(&allzero, 0, sizeof(PgStat_StatRoleEntry)); + roleentry = &allzero; + } + + /* roleid */ + values[i++] = ObjectIdGetDatum(roleid); + + /* sessions */ + values[i++] = Int64GetDatum(roleentry->sessions); + + /* xact_commit */ + values[i++] = Int64GetDatum(roleentry->xact_commit); + + /* xact_rollback */ + values[i++] = Int64GetDatum(roleentry->xact_rollback); + + /* stats_reset */ + if (roleentry->stat_reset_timestamp == 0) + nulls[i] = true; + else + values[i] = TimestampTzGetDatum(roleentry->stat_reset_timestamp); + + Assert(i + 1 == PG_STAT_GET_ROLE_STATS_COLS); + + /* Returns the record as Datum */ + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + /* * Checks for presence of stats for object with provided kind, database oid, * object oid. diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ba4e2b4d908f9..967b3948df7b4 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202607091 +#define CATALOG_VERSION_NO 202607121 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 3cb84359adf06..70acc49e89e25 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5752,6 +5752,13 @@ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}', proargnames => '{subid,subid,apply_error_count,sync_seq_error_count,sync_table_error_count,confl_insert_exists,confl_update_origin_differs,confl_update_exists,confl_update_deleted,confl_update_missing,confl_delete_origin_differs,confl_delete_missing,confl_multiple_unique_conflicts,stats_reset}', prosrc => 'pg_stat_get_subscription_stats' }, +{ oid => '9515', descr => 'statistics: information about role stats', + proname => 'pg_stat_get_role_stats', provolatile => 's', + proparallel => 'r', prorettype => 'record', proargtypes => 'oid', + proallargtypes => '{oid,oid,int8,int8,int8,timestamptz}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{roleid,roleid,sessions,xact_commit,xact_rollback,stats_reset}', + prosrc => 'pg_stat_get_role_stats' }, { oid => '6118', descr => 'statistics: information about subscription', proname => 'pg_stat_get_subscription', prorows => '10', proisstrict => 'f', proretset => 't', provolatile => 's', proparallel => 'r', @@ -6267,6 +6274,11 @@ proname => 'pg_stat_reset_subscription_stats', proisstrict => 'f', provolatile => 'v', prorettype => 'void', proargtypes => 'oid', prosrc => 'pg_stat_reset_subscription_stats', proacl => '{POSTGRES=X}' }, +{ oid => '9516', + descr => 'statistics: reset collected statistics for a single role', + proname => 'pg_stat_reset_role_stats', proisstrict => 'f', + provolatile => 'v', prorettype => 'void', proargtypes => 'oid', + prosrc => 'pg_stat_reset_role_stats', proacl => '{POSTGRES=X}' }, { oid => '3163', descr => 'current trigger depth', proname => 'pg_trigger_depth', provolatile => 's', proparallel => 'r', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 58a44857f1311..81ef032aefb3e 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -218,7 +218,7 @@ typedef struct PgStat_TableXactStatus * ------------------------------------------------------------ */ -#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBC +#define PGSTAT_FILE_FORMAT_ID 0x01A5BCBD typedef struct PgStat_ArchiverStats { @@ -448,6 +448,14 @@ typedef struct PgStat_StatSubEntry TimestampTz stat_reset_timestamp; } PgStat_StatSubEntry; +typedef struct PgStat_StatRoleEntry +{ + PgStat_Counter sessions; + PgStat_Counter xact_commit; + PgStat_Counter xact_rollback; + TimestampTz stat_reset_timestamp; +} PgStat_StatRoleEntry; + typedef struct PgStat_StatTabEntry { PgStat_Counter numscans; @@ -812,6 +820,16 @@ extern int pgstat_get_slru_index(const char *name); extern PgStat_SLRUStats *pgstat_fetch_slru(void); +/* + * Functions in pgstat_role.c + */ + +extern void pgstat_create_role(Oid roleid); +extern void pgstat_drop_role(Oid roleid); +extern void pgstat_report_role_connect(Oid roleid); +extern PgStat_StatRoleEntry *pgstat_fetch_stat_role_entry(Oid roleid); + + /* * Functions in pgstat_subscription.c */ diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index b3dc3ff7d8bb7..37d6120053be1 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -519,6 +519,12 @@ typedef struct PgStatShared_Subscription PgStat_StatSubEntry stats; } PgStatShared_Subscription; +typedef struct PgStatShared_Role +{ + PgStatShared_Common header; + PgStat_StatRoleEntry stats; +} PgStatShared_Role; + typedef struct PgStatShared_ReplSlot { PgStatShared_Common header; @@ -847,6 +853,16 @@ extern void pgstat_wal_reset_all_cb(TimestampTz ts); extern void pgstat_wal_snapshot_cb(void); +/* + * Functions in pgstat_role.c + */ + +extern void AtEOXact_PgStat_Role(bool isCommit, bool parallel); +extern void pgstat_update_role_stats(void); +extern bool pgstat_role_flush_cb(PgStat_EntryRef *entry_ref, bool nowait); +extern void pgstat_role_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts); + + /* * Functions in pgstat_subscription.c */ diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h index 2d78a02968347..0c0ae5d347d0e 100644 --- a/src/include/utils/pgstat_kind.h +++ b/src/include/utils/pgstat_kind.h @@ -30,15 +30,16 @@ #define PGSTAT_KIND_REPLSLOT 4 /* per-slot statistics */ #define PGSTAT_KIND_SUBSCRIPTION 5 /* per-subscription statistics */ #define PGSTAT_KIND_BACKEND 6 /* per-backend statistics */ +#define PGSTAT_KIND_ROLE 7 /* per-role statistics */ /* stats for fixed-numbered objects */ -#define PGSTAT_KIND_ARCHIVER 7 -#define PGSTAT_KIND_BGWRITER 8 -#define PGSTAT_KIND_CHECKPOINTER 9 -#define PGSTAT_KIND_IO 10 -#define PGSTAT_KIND_LOCK 11 -#define PGSTAT_KIND_SLRU 12 -#define PGSTAT_KIND_WAL 13 +#define PGSTAT_KIND_ARCHIVER 8 +#define PGSTAT_KIND_BGWRITER 9 +#define PGSTAT_KIND_CHECKPOINTER 10 +#define PGSTAT_KIND_IO 11 +#define PGSTAT_KIND_LOCK 12 +#define PGSTAT_KIND_SLRU 13 +#define PGSTAT_KIND_WAL 14 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE #define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 39905c2de142c..4f53d62ef5433 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2274,6 +2274,14 @@ pg_stat_replication_slots| SELECT s.slot_name, FROM pg_replication_slots r, LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, mem_exceeded_count, total_txns, total_bytes, slotsync_skip_count, slotsync_last_skip, stats_reset) WHERE (r.datoid IS NOT NULL); +pg_stat_role| SELECT rs.roleid, + r.rolname, + rs.sessions, + rs.xact_commit, + rs.xact_rollback, + rs.stats_reset + FROM pg_roles r, + LATERAL pg_stat_get_role_stats(r.oid) rs(roleid, sessions, xact_commit, xact_rollback, stats_reset); pg_stat_slru| SELECT name, blks_zeroed, blks_hit, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 03cbc1cdef59e..c45a34b054214 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -127,14 +127,15 @@ SELECT id, name, fixed_amount, 4 | replslot | f | t | t 5 | subscription | f | t | t 6 | backend | f | t | f - 7 | archiver | t | f | t - 8 | bgwriter | t | f | t - 9 | checkpointer | t | f | t - 10 | io | t | f | t - 11 | lock | t | f | t - 12 | slru | t | f | t - 13 | wal | t | f | t -(13 rows) + 7 | role | f | t | t + 8 | archiver | t | f | t + 9 | bgwriter | t | f | t + 10 | checkpointer | t | f | t + 11 | io | t | f | t + 12 | lock | t | f | t + 13 | slru | t | f | t + 14 | wal | t | f | t +(14 rows) -- ensure that both seqscan and indexscan plans are allowed SET enable_seqscan TO on; @@ -1497,6 +1498,101 @@ SELECT pg_stat_get_subscription_stats(NULL); (1 row) +SELECT pg_stat_get_role_stats(NULL); + pg_stat_get_role_stats +------------------------ + +(1 row) + +-- Test per-role statistics +CREATE ROLE regress_stats_role1; +SELECT oid AS stats_test_role1_oid FROM pg_roles + WHERE rolname = 'regress_stats_role1' \gset +-- creating a role also creates its stats entry, with all-zero counters; +-- stats_reset is NULL until the entry is explicitly reset +SELECT pg_stat_have_stats('role', 0, :stats_test_role1_oid); + pg_stat_have_stats +-------------------- + t +(1 row) + +SELECT sessions, xact_commit, xact_rollback, + stats_reset IS NOT NULL AS has_stats_reset + FROM pg_stat_role WHERE rolname = 'regress_stats_role1'; + sessions | xact_commit | xact_rollback | has_stats_reset +----------+-------------+---------------+----------------- + 0 | 0 | 0 | f +(1 row) + +-- stats entry of a rolled back CREATE ROLE is dropped again +BEGIN; +CREATE ROLE regress_stats_role2; +SELECT oid AS stats_test_role2_oid FROM pg_roles + WHERE rolname = 'regress_stats_role2' \gset +ROLLBACK; +SELECT pg_stat_have_stats('role', 0, :stats_test_role2_oid); + pg_stat_have_stats +-------------------- + f +(1 row) + +-- this session's activity is attributed to its login role +BEGIN; +SELECT 1 AS dummy; + dummy +------- + 1 +(1 row) + +COMMIT; +BEGIN; +SELECT 1 AS dummy; + dummy +------- + 1 +(1 row) + +ROLLBACK; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT sessions > 0 AS has_sessions, xact_commit > 0 AS has_xact_commit, + xact_rollback > 0 AS has_xact_rollback + FROM pg_stat_role WHERE rolname = current_user; + has_sessions | has_xact_commit | has_xact_rollback +--------------+-----------------+------------------- + t | t | t +(1 row) + +-- resetting stats of a single role sets a new reset timestamp +SELECT pg_stat_reset_role_stats(:stats_test_role1_oid); + pg_stat_reset_role_stats +-------------------------- + +(1 row) + +SELECT sessions, xact_commit, xact_rollback, + stats_reset IS NOT NULL AS has_stats_reset + FROM pg_stat_role WHERE rolname = 'regress_stats_role1'; + sessions | xact_commit | xact_rollback | has_stats_reset +----------+-------------+---------------+----------------- + 0 | 0 | 0 | t +(1 row) + +-- invalid role OID is rejected +SELECT pg_stat_reset_role_stats(0); +ERROR: invalid role OID 0 +-- dropping a role also drops its stats entry +DROP ROLE regress_stats_role1; +SELECT pg_stat_have_stats('role', 0, :stats_test_role1_oid); + pg_stat_have_stats +-------------------- + f +(1 row) + -- Test that the following operations are tracked in pg_stat_io and in -- backend stats: -- - reads of target blocks into shared buffers diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 4c265d1245c72..3c7b8fbd63c7f 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -660,6 +660,52 @@ SET enable_seqscan TO on; -- ensure that stats accessors handle NULL input correctly SELECT pg_stat_get_replication_slot(NULL); SELECT pg_stat_get_subscription_stats(NULL); +SELECT pg_stat_get_role_stats(NULL); + +-- Test per-role statistics +CREATE ROLE regress_stats_role1; +SELECT oid AS stats_test_role1_oid FROM pg_roles + WHERE rolname = 'regress_stats_role1' \gset + +-- creating a role also creates its stats entry, with all-zero counters; +-- stats_reset is NULL until the entry is explicitly reset +SELECT pg_stat_have_stats('role', 0, :stats_test_role1_oid); +SELECT sessions, xact_commit, xact_rollback, + stats_reset IS NOT NULL AS has_stats_reset + FROM pg_stat_role WHERE rolname = 'regress_stats_role1'; + +-- stats entry of a rolled back CREATE ROLE is dropped again +BEGIN; +CREATE ROLE regress_stats_role2; +SELECT oid AS stats_test_role2_oid FROM pg_roles + WHERE rolname = 'regress_stats_role2' \gset +ROLLBACK; +SELECT pg_stat_have_stats('role', 0, :stats_test_role2_oid); + +-- this session's activity is attributed to its login role +BEGIN; +SELECT 1 AS dummy; +COMMIT; +BEGIN; +SELECT 1 AS dummy; +ROLLBACK; +SELECT pg_stat_force_next_flush(); +SELECT sessions > 0 AS has_sessions, xact_commit > 0 AS has_xact_commit, + xact_rollback > 0 AS has_xact_rollback + FROM pg_stat_role WHERE rolname = current_user; + +-- resetting stats of a single role sets a new reset timestamp +SELECT pg_stat_reset_role_stats(:stats_test_role1_oid); +SELECT sessions, xact_commit, xact_rollback, + stats_reset IS NOT NULL AS has_stats_reset + FROM pg_stat_role WHERE rolname = 'regress_stats_role1'; + +-- invalid role OID is rejected +SELECT pg_stat_reset_role_stats(0); + +-- dropping a role also drops its stats entry +DROP ROLE regress_stats_role1; +SELECT pg_stat_have_stats('role', 0, :stats_test_role1_oid); -- Test that the following operations are tracked in pg_stat_io and in diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 56c1f997f88b1..bbb7e2687b1a2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2329,6 +2329,7 @@ PgStatShared_IO PgStatShared_Lock PgStatShared_Relation PgStatShared_ReplSlot +PgStatShared_Role PgStatShared_SLRU PgStatShared_Subscription PgStatShared_Wal @@ -2363,6 +2364,7 @@ PgStat_StatCustomVarEntry PgStat_StatDBEntry PgStat_StatFuncEntry PgStat_StatReplSlotEntry +PgStat_StatRoleEntry PgStat_StatSubEntry PgStat_StatTabEntry PgStat_StatsFileOp