From 6949485ab3951b6a6497f3e312b6d16ec8983210 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Thu, 31 Jul 2025 09:35:31 +0000 Subject: [PATCH v1 2/2] Add the pg_stat_lock view This new view reports lock statistics. This commit also adds documentation and a few tests. XXX: Bump catversion --- doc/src/sgml/monitoring.sgml | 151 ++++++++++++++++++ src/backend/catalog/system_views.sql | 12 ++ src/backend/utils/activity/pgstat_lock.c | 8 + src/backend/utils/adt/pgstatfuncs.c | 39 +++++ src/include/catalog/pg_proc.dat | 9 ++ src/include/pgstat.h | 1 + src/test/isolation/expected/deadlock-hard.out | 20 ++- src/test/isolation/specs/deadlock-hard.spec | 5 +- src/test/regress/expected/advisory_lock.out | 18 +++ src/test/regress/expected/rules.out | 9 ++ src/test/regress/sql/advisory_lock.sql | 4 + 11 files changed, 274 insertions(+), 2 deletions(-) 51.5% doc/src/sgml/ 3.3% src/backend/catalog/ 14.3% src/backend/utils/adt/ 5.7% src/include/catalog/ 8.4% src/test/isolation/expected/ 5.9% src/test/isolation/specs/ 6.7% src/test/regress/expected/ 3.8% src/ diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 823afe1b30b..0d18124f239 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -493,6 +493,15 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser + + pg_stat_lockpg_stat_lock + + One row for each lock type, containing cluster-wide locks statistics. + See + pg_stat_lock for details. + + + pg_stat_replication_slotspg_stat_replication_slots One row per replication slot, showing statistics about the @@ -3027,6 +3036,142 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + + <structname>pg_stat_lock</structname> + + + pg_stat_lock + + + + The pg_stat_lock view will contain one row for each + lock type, showing cluster-wide locks statistics. + + + + <structname>pg_stat_lock</structname> View + + + + + + Column Type + + + Description + + + + + + + + + locktype text + + + Type of the lockable object: + relation, + extend, + frozenid, + page, + tuple, + transactionid, + virtualxid, + spectoken, + object, + userlock, + advisory, or + applytransaction. + (See also .) + + + + + + + + requests bigint + + + Number of requests for this lock type. + + + + + + + + waits bigint + + + Number of times requests for this lock type had to wait. + + + + + + + + timeouts bigint + + + Number of times requests for this lock type had to wait longer + than lock_timeout. + + + + + + + + deadlock_timeouts bigint + + + Number of times requests for this lock type had to wait longer + than deadlock_timeout. + + + + + + + + deadlocks bigint + + + Number of times a deadlock occurred on this lock type. + + + + + + + + fastpath bigint + + + Number of times this lock type was taken via fast path. + + + + + + + + stats_reset timestamp with time zone + + + Time at which these statistics were last reset. + + + + + +
+
+ <structname>pg_stat_bgwriter</structname> @@ -5044,6 +5189,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage pg_stat_io view. + + + lock: Reset all the counters shown in the + pg_stat_lock view. + + recovery_prefetch: Reset all the counters shown in diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index f6eca09ee15..d493a00c544 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -950,6 +950,18 @@ CREATE VIEW pg_stat_slru AS s.stats_reset FROM pg_stat_get_slru() s; +CREATE VIEW pg_stat_lock AS + SELECT + l.locktype, + l.requests, + l.waits, + l.timeouts, + l.deadlock_timeouts, + l.deadlocks, + l.fastpath, + l.stats_reset + FROM pg_stat_get_lock() l; + CREATE VIEW pg_stat_wal_receiver AS SELECT s.pid, diff --git a/src/backend/utils/activity/pgstat_lock.c b/src/backend/utils/activity/pgstat_lock.c index fb4ed5c7362..73bdeb5ff66 100644 --- a/src/backend/utils/activity/pgstat_lock.c +++ b/src/backend/utils/activity/pgstat_lock.c @@ -21,6 +21,14 @@ static PgStat_PendingLock PendingLockStats; static bool have_lockstats = false; +PgStat_Lock * +pgstat_fetch_stat_lock(void) +{ + pgstat_snapshot_fixed(PGSTAT_KIND_LOCK); + + return &pgStatLocal.snapshot.lock; +} + /* * Simpler wrapper of pgstat_lock_flush_cb() */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1c12ddbae49..5e63f2d2a38 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1696,6 +1696,42 @@ pg_stat_get_wal(PG_FUNCTION_ARGS) wal_stats->stat_reset_timestamp)); } +Datum +pg_stat_get_lock(PG_FUNCTION_ARGS) +{ +#define PG_STAT_LOCK_COLS 8 + ReturnSetInfo *rsinfo; + PgStat_Lock *lock_stats; + + InitMaterializedSRF(fcinfo, 0); + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + lock_stats = pgstat_fetch_stat_lock(); + + for (int lcktype = 0; lcktype <= LOCKTAG_LAST_TYPE; lcktype++) + { + const char *locktypename; + Datum values[PG_STAT_LOCK_COLS] = {0}; + bool nulls[PG_STAT_LOCK_COLS] = {0}; + PgStat_LockEntry *lck_stats = &lock_stats->stats[lcktype]; + + locktypename = LockTagTypeNames[lcktype]; + + values[0] = CStringGetTextDatum(locktypename); + values[1] = Int64GetDatum(lck_stats->requests); + values[2] = Int64GetDatum(lck_stats->waits); + values[3] = Int64GetDatum(lck_stats->timeouts); + values[4] = Int64GetDatum(lck_stats->deadlock_timeouts); + values[5] = Int64GetDatum(lck_stats->deadlocks); + values[6] = Int64GetDatum(lck_stats->fastpath); + values[7] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + /* * Returns statistics of SLRU caches. */ @@ -1880,6 +1916,7 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS) pgstat_reset_of_kind(PGSTAT_KIND_BGWRITER); pgstat_reset_of_kind(PGSTAT_KIND_CHECKPOINTER); pgstat_reset_of_kind(PGSTAT_KIND_IO); + pgstat_reset_of_kind(PGSTAT_KIND_LOCK); XLogPrefetchResetStats(); pgstat_reset_of_kind(PGSTAT_KIND_SLRU); pgstat_reset_of_kind(PGSTAT_KIND_WAL); @@ -1897,6 +1934,8 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS) pgstat_reset_of_kind(PGSTAT_KIND_CHECKPOINTER); else if (strcmp(target, "io") == 0) pgstat_reset_of_kind(PGSTAT_KIND_IO); + else if (strcmp(target, "lock") == 0) + pgstat_reset_of_kind(PGSTAT_KIND_LOCK); else if (strcmp(target, "recovery_prefetch") == 0) XLogPrefetchResetStats(); else if (strcmp(target, "slru") == 0) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 3ee8fed7e53..c5faff30317 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6001,6 +6001,15 @@ proargnames => '{backend_type,object,context,reads,read_bytes,read_time,writes,write_bytes,write_time,writebacks,writeback_time,extends,extend_bytes,extend_time,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}', prosrc => 'pg_stat_get_io' }, +{ oid => '9375', descr => 'statistics: per lock type statistics', + proname => 'pg_stat_get_lock', prorows => '10', proretset => 't', + provolatile => 'v', proparallel => 'r', prorettype => 'record', + proargtypes => '', + proallargtypes => '{text,int8,int8,int8,int8,int8,int8,timestamptz}', + proargmodes => '{o,o,o,o,o,o,o,o}', + proargnames => '{locktype,requests,waits,timeouts,deadlock_timeouts,deadlocks,fastpath,stats_reset}', + prosrc => 'pg_stat_get_lock' }, + { oid => '6386', descr => 'statistics: backend IO statistics', proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't', provolatile => 'v', proparallel => 'r', prorettype => 'record', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 7893f7311ae..e653bce4aab 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -636,6 +636,7 @@ extern void pgstat_count_lock_timeouts(uint8 locktag_type); extern void pgstat_count_lock_deadlock_timeouts(uint8 locktag_type); extern void pgstat_count_lock_deadlocks(uint8 locktag_type); extern void pgstat_count_lock_fastpath(uint8 locktag_type); +extern PgStat_Lock *pgstat_fetch_stat_lock(void); /* * Functions in pgstat_database.c diff --git a/src/test/isolation/expected/deadlock-hard.out b/src/test/isolation/expected/deadlock-hard.out index 460653f2b86..ff448da5299 100644 --- a/src/test/isolation/expected/deadlock-hard.out +++ b/src/test/isolation/expected/deadlock-hard.out @@ -1,6 +1,12 @@ Parsed test spec with 8 sessions -starting permutation: s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8 s8a1 s8c s7c s6c s5c s4c s3c s2c s1c +starting permutation: s1rl s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8 s8a1 s8c s8f s7c s6c s5c s4c s3c s2c s1c s1sl +step s1rl: SELECT pg_stat_reset_shared('lock'); +pg_stat_reset_shared +-------------------- + +(1 row) + step s1a1: LOCK TABLE a1; step s2a2: LOCK TABLE a2; step s3a3: LOCK TABLE a3; @@ -21,6 +27,12 @@ step s8a1: <... completed> ERROR: deadlock detected step s7a8: <... completed> step s8c: COMMIT; +step s8f: SELECT pg_stat_force_next_flush(); +pg_stat_force_next_flush +------------------------ + +(1 row) + step s7c: COMMIT; step s6a7: <... completed> step s6c: COMMIT; @@ -34,3 +46,9 @@ step s2a3: <... completed> step s2c: COMMIT; step s1a2: <... completed> step s1c: COMMIT; +step s1sl: SELECT deadlocks > 0, deadlock_timeouts > 0 FROM pg_stat_lock WHERE locktype = 'relation'; +?column?|?column? +--------+-------- +t |t +(1 row) + diff --git a/src/test/isolation/specs/deadlock-hard.spec b/src/test/isolation/specs/deadlock-hard.spec index 60bedca237a..6e8330662b4 100644 --- a/src/test/isolation/specs/deadlock-hard.spec +++ b/src/test/isolation/specs/deadlock-hard.spec @@ -25,6 +25,8 @@ setup { BEGIN; SET deadlock_timeout = '100s'; } step s1a1 { LOCK TABLE a1; } step s1a2 { LOCK TABLE a2; } step s1c { COMMIT; } +step s1sl { SELECT deadlocks > 0, deadlock_timeouts > 0 FROM pg_stat_lock WHERE locktype = 'relation'; } +step s1rl { SELECT pg_stat_reset_shared('lock'); } session s2 setup { BEGIN; SET deadlock_timeout = '100s'; } @@ -67,6 +69,7 @@ setup { BEGIN; SET deadlock_timeout = '10ms'; } step s8a8 { LOCK TABLE a8; } step s8a1 { LOCK TABLE a1; } step s8c { COMMIT; } +step s8f { SELECT pg_stat_force_next_flush(); } # Note: when s8a1 detects the deadlock and fails, s7a8 is released, making # it timing-dependent which query completion is received first by the tester. @@ -76,4 +79,4 @@ step s8c { COMMIT; } # dummy blocking mark to s8a1 to ensure it will be reported as "waiting" # regardless of that. -permutation s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8(s8a1) s8a1(*) s8c s7c s6c s5c s4c s3c s2c s1c +permutation s1rl s1a1 s2a2 s3a3 s4a4 s5a5 s6a6 s7a7 s8a8 s1a2 s2a3 s3a4 s4a5 s5a6 s6a7 s7a8(s8a1) s8a1(*) s8c s8f s7c s6c s5c s4c s3c s2c s1c s1sl diff --git a/src/test/regress/expected/advisory_lock.out b/src/test/regress/expected/advisory_lock.out index 02e07765ac2..fdaa1756ba4 100644 --- a/src/test/regress/expected/advisory_lock.out +++ b/src/test/regress/expected/advisory_lock.out @@ -2,6 +2,12 @@ -- ADVISORY LOCKS -- SELECT oid AS datoid FROM pg_database WHERE datname = current_database() \gset +SELECT pg_stat_reset_shared('lock'); + pg_stat_reset_shared +---------------------- + +(1 row) + BEGIN; SELECT pg_advisory_xact_lock(1), pg_advisory_xact_lock_shared(2), @@ -48,6 +54,12 @@ WARNING: you don't own a lock of type ShareLock f | f | f | f (1 row) +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + -- automatically release xact locks at commit COMMIT; SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; @@ -56,6 +68,12 @@ SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid 0 (1 row) +SELECT requests FROM pg_stat_lock WHERE locktype = 'advisory'; + requests +---------- + 4 +(1 row) + BEGIN; -- holding both session and xact locks on the same objects, xact first SELECT diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index dce8c672b40..c790164ea82 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1938,6 +1938,15 @@ pg_stat_io| SELECT backend_type, fsync_time, stats_reset FROM pg_stat_get_io() b(backend_type, object, context, reads, read_bytes, read_time, writes, write_bytes, write_time, writebacks, writeback_time, extends, extend_bytes, extend_time, hits, evictions, reuses, fsyncs, fsync_time, stats_reset); +pg_stat_lock| SELECT locktype, + requests, + waits, + timeouts, + deadlock_timeouts, + deadlocks, + fastpath, + stats_reset + FROM pg_stat_get_lock() l(locktype, requests, waits, timeouts, deadlock_timeouts, deadlocks, fastpath, stats_reset); pg_stat_progress_analyze| SELECT s.pid, s.datid, d.datname, diff --git a/src/test/regress/sql/advisory_lock.sql b/src/test/regress/sql/advisory_lock.sql index 8513ab8e98f..f1bff60fd37 100644 --- a/src/test/regress/sql/advisory_lock.sql +++ b/src/test/regress/sql/advisory_lock.sql @@ -4,6 +4,8 @@ SELECT oid AS datoid FROM pg_database WHERE datname = current_database() \gset +SELECT pg_stat_reset_shared('lock'); + BEGIN; SELECT @@ -26,12 +28,14 @@ SELECT pg_advisory_unlock(1), pg_advisory_unlock_shared(2), pg_advisory_unlock(1, 1), pg_advisory_unlock_shared(2, 2); +SELECT pg_stat_force_next_flush(); -- automatically release xact locks at commit COMMIT; SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND database = :datoid; +SELECT requests FROM pg_stat_lock WHERE locktype = 'advisory'; BEGIN; -- 2.34.1