From 70c5c5bbf85d7e5aa94b1cf0c2499d9ff6dbf527 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Date: Tue, 29 Jul 2025 08:36:35 +0000
Subject: [PATCH v3 1/2] Add lock statistics

Adding a new stat kind PGSTAT_KIND_LOCK for the lock statistics.

This new statistic kind is a fixed one because its key is the lock type
so that we know its size is LOCKTAG_LAST_TYPE + 1.

This statistic kind records the following counters:

requests
waits
timeouts
deadlock_timeouts
deadlocks
fastpath

No extra details is added (like the ones, i.e relation oid, database oid, we
can find in pg_locks). The idea is to provide an idea on what the locking
behaviour looks like.

XXX: Bump stat file format
---
 src/backend/storage/lmgr/lock.c          |  18 +++
 src/backend/storage/lmgr/proc.c          |   2 +
 src/backend/tcop/postgres.c              |   8 ++
 src/backend/utils/activity/Makefile      |   1 +
 src/backend/utils/activity/meson.build   |   1 +
 src/backend/utils/activity/pgstat.c      |  18 +++
 src/backend/utils/activity/pgstat_lock.c | 168 +++++++++++++++++++++++
 src/include/pgstat.h                     |  34 +++++
 src/include/utils/pgstat_internal.h      |  21 +++
 src/include/utils/pgstat_kind.h          |   5 +-
 src/tools/pgindent/typedefs.list         |   4 +
 11 files changed, 278 insertions(+), 2 deletions(-)
  13.3% src/backend/storage/lmgr/
  62.5% src/backend/utils/activity/
   8.3% src/include/utils/
  12.1% src/include/
   3.6% src/

diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 7f0cd784f79..bda257a3543 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -45,6 +45,7 @@
 #include "storage/spin.h"
 #include "storage/standby.h"
 #include "utils/memutils.h"
+#include "utils/pgstat_internal.h"
 #include "utils/ps_status.h"
 #include "utils/resowner.h"
 
@@ -871,6 +872,9 @@ LockAcquireExtended(const LOCKTAG *locktag,
 						lockMethodTable->lockModeNames[lockmode]),
 				 errhint("Only RowExclusiveLock or less can be acquired on database objects during recovery.")));
 
+	/* Increment the lock statistics requests counter */
+	pgstat_count_lock_requests(locktag->locktag_type);
+
 #ifdef LOCK_DEBUG
 	if (LOCK_DEBUG_ENABLED(locktag))
 		elog(LOG, "LockAcquire: lock [%u,%u] %s",
@@ -1202,6 +1206,8 @@ LockAcquireExtended(const LOCKTAG *locktag,
 		}
 		else
 		{
+			/* Increment the lock statistics deadlocks counter */
+			pgstat_count_lock_deadlocks(locallock->tag.lock.locktag_type);
 			DeadLockReport();
 			/* DeadLockReport() will not return */
 		}
@@ -1213,6 +1219,8 @@ LockAcquireExtended(const LOCKTAG *locktag,
 	 */
 	if (waitResult == PROC_WAIT_STATUS_WAITING)
 	{
+		/* Increment the lock statistics waits counter */
+		pgstat_count_lock_waits(locktag->locktag_type);
 		Assert(!dontWait);
 		PROCLOCK_PRINT("LockAcquire: sleeping on lock", proclock);
 		LOCK_PRINT("LockAcquire: sleeping on lock", lock, lockmode);
@@ -1233,6 +1241,8 @@ LockAcquireExtended(const LOCKTAG *locktag,
 			 * now.
 			 */
 			Assert(!dontWait);
+			/* Increment the lock statistics deadlocks counter */
+			pgstat_count_lock_deadlocks(locallock->tag.lock.locktag_type);
 			DeadLockReport();
 			/* DeadLockReport() will not return */
 		}
@@ -2799,6 +2809,8 @@ FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode)
 		{
 			Assert(!FAST_PATH_CHECK_LOCKMODE(MyProc, f, lockmode));
 			FAST_PATH_SET_LOCKMODE(MyProc, f, lockmode);
+			/* Increment the lock statistics fastpath counter */
+			pgstat_count_lock_fastpath(LOCKTAG_RELATION);
 			return true;
 		}
 	}
@@ -2808,6 +2820,8 @@ FastPathGrantRelationLock(Oid relid, LOCKMODE lockmode)
 	{
 		MyProc->fpRelId[unused_slot] = relid;
 		FAST_PATH_SET_LOCKMODE(MyProc, unused_slot, lockmode);
+		/* Increment the lock statistics fastpath counter */
+		pgstat_count_lock_fastpath(LOCKTAG_RELATION);
 		++FastPathLocalUseCounts[group];
 		return true;
 	}
@@ -4630,6 +4644,10 @@ VirtualXactLockTableInsert(VirtualTransactionId vxid)
 	MyProc->fpVXIDLock = true;
 	MyProc->fpLocalTransactionId = vxid.localTransactionId;
 
+	/* Increment the lock statistics requests and fastpath counters */
+	pgstat_count_lock_requests(LOCKTAG_VIRTUALTRANSACTION);
+	pgstat_count_lock_fastpath(LOCKTAG_VIRTUALTRANSACTION);
+
 	LWLockRelease(&MyProc->fpInfoLock);
 }
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 8560a903bc8..2b3e1dfea5c 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -1460,6 +1460,8 @@ ProcSleep(LOCALLOCK *locallock)
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
+				/* Increment the lock statistics deadlock_timeouts counter */
+				pgstat_count_lock_deadlock_timeouts(locallock->tag.lock.locktag_type);
 				deadlock_state = CheckDeadLock();
 				got_deadlock_timeout = false;
 			}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 02e9aaa6bca..ed8b2e27666 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3417,6 +3417,14 @@ ProcessInterrupts(void)
 
 		if (lock_timeout_occurred)
 		{
+			LOCALLOCK  *lockAwaited;
+
+			lockAwaited = GetAwaitedLock();
+
+			/* Increment the lock statistics timeouts counter */
+			if (lockAwaited)
+				pgstat_count_lock_timeouts(lockAwaited->tag.lock.locktag_type);
+
 			LockErrorCleanup();
 			ereport(ERROR,
 					(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index c37bfb350bb..ca3ef89bf59 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -26,6 +26,7 @@ OBJS = \
 	pgstat_database.o \
 	pgstat_function.o \
 	pgstat_io.o \
+	pgstat_lock.o \
 	pgstat_relation.o \
 	pgstat_replslot.o \
 	pgstat_shmem.o \
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 53bd5a246ca..1aa7ece5290 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'pgstat_database.c',
   'pgstat_function.c',
   'pgstat_io.c',
+  'pgstat_lock.c',
   'pgstat_relation.c',
   'pgstat_replslot.c',
   'pgstat_shmem.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 11bb71cad5a..eb8ccbaa628 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -83,6 +83,7 @@
  * - pgstat_database.c
  * - pgstat_function.c
  * - pgstat_io.c
+ * - pgstat_lock.c
  * - pgstat_relation.c
  * - pgstat_replslot.c
  * - pgstat_slru.c
@@ -448,6 +449,23 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.snapshot_cb = pgstat_io_snapshot_cb,
 	},
 
+	[PGSTAT_KIND_LOCK] = {
+		.name = "lock",
+
+		.fixed_amount = true,
+		.write_to_file = true,
+
+		.snapshot_ctl_off = offsetof(PgStat_Snapshot, lock),
+		.shared_ctl_off = offsetof(PgStat_ShmemControl, lock),
+		.shared_data_off = offsetof(PgStatShared_Lock, stats),
+		.shared_data_len = sizeof(((PgStatShared_Lock *) 0)->stats),
+
+		.flush_static_cb = pgstat_lock_flush_cb,
+		.init_shmem_cb = pgstat_lock_init_shmem_cb,
+		.reset_all_cb = pgstat_lock_reset_all_cb,
+		.snapshot_cb = pgstat_lock_snapshot_cb,
+	},
+
 	[PGSTAT_KIND_SLRU] = {
 		.name = "slru",
 
diff --git a/src/backend/utils/activity/pgstat_lock.c b/src/backend/utils/activity/pgstat_lock.c
new file mode 100644
index 00000000000..fb4ed5c7362
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_lock.c
@@ -0,0 +1,168 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_lock.c
+ *	  Implementation of lock statistics.
+ *
+ * This file contains the implementation of lock 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.
+ *
+ * Copyright (c) 2021-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_lock.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "utils/pgstat_internal.h"
+
+static PgStat_PendingLock PendingLockStats;
+static bool have_lockstats = false;
+
+/*
+ * Simpler wrapper of pgstat_lock_flush_cb()
+ */
+void
+pgstat_lock_flush(bool nowait)
+{
+	(void) pgstat_lock_flush_cb(nowait);
+}
+
+/*
+ * Flush out locally pending lock statistics
+ *
+ * If no stats have been recorded, this function returns false.
+ *
+ * If nowait is true, this function returns true if the lock could not be
+ * acquired. Otherwise, return false.
+ */
+bool
+pgstat_lock_flush_cb(bool nowait)
+{
+	LWLock	   *lcktype_lock;
+	PgStat_LockEntry *lck_shstats;
+	bool		lock_not_acquired = false;
+
+	if (!have_lockstats)
+		return false;
+
+	for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++)
+	{
+		lcktype_lock = &pgStatLocal.shmem->lock.locks[i];
+		lck_shstats =
+			&pgStatLocal.shmem->lock.stats.stats[i];
+
+		if (!nowait)
+			LWLockAcquire(lcktype_lock, LW_EXCLUSIVE);
+		else if (!LWLockConditionalAcquire(lcktype_lock, LW_EXCLUSIVE))
+		{
+			lock_not_acquired = true;
+			continue;
+		}
+
+#define LOCKSTAT_ACC(fld) \
+	(lck_shstats->fld += PendingLockStats.stats[i].fld)
+		LOCKSTAT_ACC(requests);
+		LOCKSTAT_ACC(waits);
+		LOCKSTAT_ACC(timeouts);
+		LOCKSTAT_ACC(deadlock_timeouts);
+		LOCKSTAT_ACC(deadlocks);
+		LOCKSTAT_ACC(fastpath);
+#undef LOCKSTAT_ACC
+
+		LWLockRelease(lcktype_lock);
+	}
+
+	memset(&PendingLockStats, 0, sizeof(PendingLockStats));
+
+	have_lockstats = false;
+
+	return lock_not_acquired;
+}
+
+
+void
+pgstat_lock_init_shmem_cb(void *stats)
+{
+	PgStatShared_Lock *stat_shmem = (PgStatShared_Lock *) stats;
+
+	for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++)
+		LWLockInitialize(&stat_shmem->locks[i], LWTRANCHE_PGSTATS_DATA);
+}
+
+void
+pgstat_lock_reset_all_cb(TimestampTz ts)
+{
+	for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++)
+	{
+		LWLock	   *lcktype_lock = &pgStatLocal.shmem->lock.locks[i];
+		PgStat_LockEntry *lck_shstats = &pgStatLocal.shmem->lock.stats.stats[i];
+
+		LWLockAcquire(lcktype_lock, LW_EXCLUSIVE);
+
+		/*
+		 * Use the lock in the first lock type PgStat_LockEntry to protect the
+		 * reset timestamp as well.
+		 */
+		if (i == 0)
+			pgStatLocal.shmem->lock.stats.stat_reset_timestamp = ts;
+
+		memset(lck_shstats, 0, sizeof(*lck_shstats));
+		LWLockRelease(lcktype_lock);
+	}
+}
+
+void
+pgstat_lock_snapshot_cb(void)
+{
+	for (int i = 0; i <= LOCKTAG_LAST_TYPE; i++)
+	{
+		LWLock	   *lcktype_lock = &pgStatLocal.shmem->lock.locks[i];
+		PgStat_LockEntry *lck_shstats = &pgStatLocal.shmem->lock.stats.stats[i];
+		PgStat_LockEntry *lck_snap = &pgStatLocal.snapshot.lock.stats[i];
+
+		LWLockAcquire(lcktype_lock, LW_SHARED);
+
+		/*
+		 * Use the lock in the first lock type PgStat_LockEntry to protect the
+		 * reset timestamp as well.
+		 */
+		if (i == 0)
+			pgStatLocal.snapshot.lock.stat_reset_timestamp =
+				pgStatLocal.shmem->lock.stats.stat_reset_timestamp;
+
+		/* using struct assignment due to better type safety */
+		*lck_snap = *lck_shstats;
+		LWLockRelease(lcktype_lock);
+	}
+}
+
+#define PGSTAT_COUNT_LOCK_FUNC(stat)					\
+void													\
+CppConcat(pgstat_count_lock_,stat)(uint8 locktag_type)	\
+{														\
+	Assert(locktag_type <= LOCKTAG_LAST_TYPE);			\
+	PendingLockStats.stats[locktag_type].stat++;		\
+	have_lockstats = true;								\
+	pgstat_report_fixed = true;							\
+}
+
+/* pgstat_count_lock_requests */
+PGSTAT_COUNT_LOCK_FUNC(requests)
+
+/* pgstat_count_lock_waits */
+PGSTAT_COUNT_LOCK_FUNC(waits)
+
+/* pgstat_count_lock_timeouts */
+PGSTAT_COUNT_LOCK_FUNC(timeouts)
+
+/* pgstat_count_lock_deadlock_timeouts */
+PGSTAT_COUNT_LOCK_FUNC(deadlock_timeouts)
+
+/* pgstat_count_lock_deadlocks */
+PGSTAT_COUNT_LOCK_FUNC(deadlocks)
+
+/* pgstat_count_lock_fastpath */
+PGSTAT_COUNT_LOCK_FUNC(fastpath)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fff7ecc2533..cbe226b9a07 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -17,6 +17,7 @@
 #include "postmaster/pgarch.h"	/* for MAX_XFN_CHARS */
 #include "replication/conflict.h"
 #include "replication/worker_internal.h"
+#include "storage/lock.h"
 #include "utils/backend_progress.h" /* for backward compatibility */	/* IWYU pragma: export */
 #include "utils/backend_status.h"	/* for backward compatibility */	/* IWYU pragma: export */
 #include "utils/pgstat_kind.h"
@@ -342,6 +343,27 @@ typedef struct PgStat_IO
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
 } PgStat_IO;
 
+typedef struct PgStat_LockEntry
+{
+	PgStat_Counter requests;
+	PgStat_Counter waits;
+	PgStat_Counter timeouts;
+	PgStat_Counter deadlock_timeouts;
+	PgStat_Counter deadlocks;
+	PgStat_Counter fastpath;
+} PgStat_LockEntry;
+
+typedef struct PgStat_PendingLock
+{
+	PgStat_LockEntry stats[LOCKTAG_LAST_TYPE + 1];
+} PgStat_PendingLock;
+
+typedef struct PgStat_Lock
+{
+	TimestampTz stat_reset_timestamp;
+	PgStat_LockEntry stats[LOCKTAG_LAST_TYPE + 1];
+} PgStat_Lock;
+
 typedef struct PgStat_StatDBEntry
 {
 	PgStat_Counter xact_commit;
@@ -614,6 +636,18 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 								IOContext io_context, IOOp io_op);
 
 
+/*
+ * Functions in pgstat_lock.c
+ */
+
+extern void pgstat_lock_flush(bool nowait);
+extern void pgstat_count_lock_requests(uint8 locktag_type);
+extern void pgstat_count_lock_waits(uint8 locktag_type);
+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);
+
 /*
  * Functions in pgstat_database.c
  */
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..97704421a92 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -464,6 +464,16 @@ typedef struct PgStatShared_IO
 	PgStat_IO	stats;
 } PgStatShared_IO;
 
+typedef struct PgStatShared_Lock
+{
+	/*
+	 * locks[i] protects stats.stats[i]. locks[0] also protects
+	 * stats.stat_reset_timestamp.
+	 */
+	LWLock		locks[LOCKTAG_LAST_TYPE + 1];
+	PgStat_Lock stats;
+} PgStatShared_Lock;
+
 typedef struct PgStatShared_SLRU
 {
 	/* lock protects ->stats */
@@ -570,6 +580,7 @@ typedef struct PgStat_ShmemControl
 	PgStatShared_BgWriter bgwriter;
 	PgStatShared_Checkpointer checkpointer;
 	PgStatShared_IO io;
+	PgStatShared_Lock lock;
 	PgStatShared_SLRU slru;
 	PgStatShared_Wal wal;
 
@@ -602,6 +613,8 @@ typedef struct PgStat_Snapshot
 
 	PgStat_IO	io;
 
+	PgStat_Lock lock;
+
 	PgStat_SLRUStats slru[SLRU_NUM_ELEMENTS];
 
 	PgStat_WalStats wal;
@@ -752,6 +765,14 @@ extern void pgstat_io_init_shmem_cb(void *stats);
 extern void pgstat_io_reset_all_cb(TimestampTz ts);
 extern void pgstat_io_snapshot_cb(void);
 
+/*
+ * Functions in pgstat_lock.c
+ */
+
+extern bool pgstat_lock_flush_cb(bool nowait);
+extern void pgstat_lock_init_shmem_cb(void *stats);
+extern void pgstat_lock_reset_all_cb(TimestampTz ts);
+extern void pgstat_lock_snapshot_cb(void);
 
 /*
  * Functions in pgstat_relation.c
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index c30b6235623..2d78a029683 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -36,8 +36,9 @@
 #define PGSTAT_KIND_BGWRITER	8
 #define PGSTAT_KIND_CHECKPOINTER	9
 #define PGSTAT_KIND_IO	10
-#define PGSTAT_KIND_SLRU	11
-#define PGSTAT_KIND_WAL	12
+#define PGSTAT_KIND_LOCK	11
+#define PGSTAT_KIND_SLRU	12
+#define PGSTAT_KIND_WAL	13
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
 #define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7619845fba9..7386db6e6a4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2253,6 +2253,7 @@ PgStatShared_Database
 PgStatShared_Function
 PgStatShared_HashEntry
 PgStatShared_IO
+PgStatShared_Lock
 PgStatShared_Relation
 PgStatShared_ReplSlot
 PgStatShared_SLRU
@@ -2275,8 +2276,11 @@ PgStat_HashKey
 PgStat_IO
 PgStat_KindInfo
 PgStat_LocalState
+PgStat_Lock
+PgStat_LockEntry
 PgStat_PendingDroppedStatsItem
 PgStat_PendingIO
+PgStat_PendingLock
 PgStat_SLRUStats
 PgStat_ShmemControl
 PgStat_Snapshot
-- 
2.34.1

