agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v7 15/16] Trigger authors need not worry about parallelism
14+ messages / 3 participants
[nested] [flat]

* [PATCH v7 15/16] Trigger authors need not worry about parallelism
@ 2023-10-02 19:55 Karl O. Pinc <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Karl O. Pinc @ 2023-10-02 19:55 UTC (permalink / raw)

The idea is that it's worth telling the trigger author who comes
across PARALLEL declarations when creating trigger functions that they
don't need to investigate further.

Is this stating the obvious?  Yes.  But it's not obvious until you've
read about PGs parallelism, and read _enough_ to know that there's
only mention of queries and no mention of data modification
statements.  Knowing that something is missing from PG requires at
least passing familiarity with _all_ of the documentation.

Plus, this patch adds an index entry so the new verbage is easy to find
for those who do investigate.

Agreed, documenting everything PG does _not_ do is a terrible idea.
The goal here is to help trigger authors limit what they need to
research.
---
 doc/src/sgml/trigger.sgml | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 6e1f370b21..f6ec8d2a97 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -377,6 +377,17 @@
     trigger.
    </para>
 
+   <indexterm>
+     <primary>parallel safety</primary>
+     <secondary>triggers</secondary>
+   </indexterm>
+
+   <para>
+     Trigger authors need not concern themselves with parallel safety.
+     <link linkend="parallel-query">Parallel execution</link>
+     is supported only in queries, 
+   </para>
+
    <para>
     Each programming language that supports triggers has its own method
     for making the trigger input data available to the trigger function.
-- 
2.30.2


--MP_/VSGM3xNEmY7iJyL2wuWRCjV
Content-Type: text/x-patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename=v7-0016-Predicate-locks-are-held-per-cluster-not-per-data.patch



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v6 15/15] Trigger authors need not worry about parallelism
@ 2023-10-02 19:55 Karl O. Pinc <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Karl O. Pinc @ 2023-10-02 19:55 UTC (permalink / raw)

The idea is that it's worth telling the trigger author who comes
across PARALLEL declarations when creating trigger functions that they
don't need to investigate further.

Is this stating the obvious?  Yes.  But it's not obvious until you've
read about PGs parallelism, and read _enough_ to know that there's
only mention of queries and no mention of data modification
statements.  Knowing that something is missing from PG requires at
least passing familiarity with _all_ of the documentation.

Plus, this patch adds an index entry so the new verbage is easy to find
for those who do investigate.

Agreed, documenting everything PG does _not_ do is a terrible idea.
The goal here is to help trigger authors limit what they need to
research.
---
 doc/src/sgml/trigger.sgml | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 6e1f370b21..f6ec8d2a97 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -377,6 +377,17 @@
     trigger.
    </para>
 
+   <indexterm>
+     <primary>parallel safety</primary>
+     <secondary>triggers</secondary>
+   </indexterm>
+
+   <para>
+     Trigger authors need not concern themselves with parallel safety.
+     <link linkend="parallel-query">Parallel execution</link>
+     is supported only in queries, 
+   </para>
+
    <para>
     Each programming language that supports triggers has its own method
     for making the trigger input data available to the trigger function.
-- 
2.30.2


--MP_/74urtnrsBSymuH7bJczNOGS--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v6 2/2] Add support for AVX2 in simd.h.
@ 2024-03-18 16:02 Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Nathan Bossart @ 2024-03-18 16:02 UTC (permalink / raw)

Discussion: https://postgr.es/m/20231129171526.GA857928%40nathanxps13
---
 src/include/port/simd.h | 61 ++++++++++++++++++++++++++++++++---------
 1 file changed, 48 insertions(+), 13 deletions(-)

diff --git a/src/include/port/simd.h b/src/include/port/simd.h
index 597496f2fb..f06b21876b 100644
--- a/src/include/port/simd.h
+++ b/src/include/port/simd.h
@@ -18,7 +18,18 @@
 #ifndef SIMD_H
 #define SIMD_H
 
-#if (defined(__x86_64__) || defined(_M_AMD64))
+#if defined(__AVX2__)
+
+/*
+ * XXX: Need to add a big comment here.
+ */
+#include <immintrin.h>
+#define USE_AVX2
+typedef __m256i Vector8;
+typedef __m256i Vector32;
+
+#elif (defined(__x86_64__) || defined(_M_AMD64))
+
 /*
  * SSE2 instructions are part of the spec for the 64-bit x86 ISA. We assume
  * that compilers targeting this architecture understand SSE2 intrinsics.
@@ -107,7 +118,9 @@ static inline Vector32 vector32_eq(const Vector32 v1, const Vector32 v2);
 static inline void
 vector8_load(Vector8 *v, const uint8 *s)
 {
-#if defined(USE_SSE2)
+#if defined(USE_AVX2)
+	*v = _mm256_loadu_si256((const __m256i *) s);
+#elif defined(USE_SSE2)
 	*v = _mm_loadu_si128((const __m128i *) s);
 #elif defined(USE_NEON)
 	*v = vld1q_u8(s);
@@ -120,7 +133,9 @@ vector8_load(Vector8 *v, const uint8 *s)
 static inline void
 vector32_load(Vector32 *v, const uint32 *s)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	*v = _mm256_loadu_si256((const __m256i *) s);
+#elif defined(USE_SSE2)
 	*v = _mm_loadu_si128((const __m128i *) s);
 #elif defined(USE_NEON)
 	*v = vld1q_u32(s);
@@ -134,7 +149,9 @@ vector32_load(Vector32 *v, const uint32 *s)
 static inline Vector8
 vector8_broadcast(const uint8 c)
 {
-#if defined(USE_SSE2)
+#if defined(USE_AVX2)
+	return _mm256_set1_epi8(c);
+#elif defined(USE_SSE2)
 	return _mm_set1_epi8(c);
 #elif defined(USE_NEON)
 	return vdupq_n_u8(c);
@@ -147,7 +164,9 @@ vector8_broadcast(const uint8 c)
 static inline Vector32
 vector32_broadcast(const uint32 c)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_set1_epi32(c);
+#elif defined(USE_SSE2)
 	return _mm_set1_epi32(c);
 #elif defined(USE_NEON)
 	return vdupq_n_u32(c);
@@ -270,7 +289,9 @@ vector8_has_le(const Vector8 v, const uint8 c)
 static inline bool
 vector8_is_highbit_set(const Vector8 v)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_movemask_epi8(v) != 0;
+#elif defined(USE_SSE2)
 	return _mm_movemask_epi8(v) != 0;
 #elif defined(USE_NEON)
 	return vmaxvq_u8(v) > 0x7F;
@@ -308,7 +329,9 @@ vector32_is_highbit_set(const Vector32 v)
 static inline uint32
 vector8_highbit_mask(const Vector8 v)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return (uint32) _mm256_movemask_epi8(v);
+#elif defined(USE_SSE2)
 	return (uint32) _mm_movemask_epi8(v);
 #elif defined(USE_NEON)
 	/*
@@ -337,7 +360,9 @@ vector8_highbit_mask(const Vector8 v)
 static inline Vector8
 vector8_or(const Vector8 v1, const Vector8 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_or_si256(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_or_si128(v1, v2);
 #elif defined(USE_NEON)
 	return vorrq_u8(v1, v2);
@@ -350,7 +375,9 @@ vector8_or(const Vector8 v1, const Vector8 v2)
 static inline Vector32
 vector32_or(const Vector32 v1, const Vector32 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_or_si256(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_or_si128(v1, v2);
 #elif defined(USE_NEON)
 	return vorrq_u32(v1, v2);
@@ -368,7 +395,9 @@ vector32_or(const Vector32 v1, const Vector32 v2)
 static inline Vector8
 vector8_ssub(const Vector8 v1, const Vector8 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_subs_epu8(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_subs_epu8(v1, v2);
 #elif defined(USE_NEON)
 	return vqsubq_u8(v1, v2);
@@ -384,7 +413,9 @@ vector8_ssub(const Vector8 v1, const Vector8 v2)
 static inline Vector8
 vector8_eq(const Vector8 v1, const Vector8 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_cmpeq_epi8(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_cmpeq_epi8(v1, v2);
 #elif defined(USE_NEON)
 	return vceqq_u8(v1, v2);
@@ -396,7 +427,9 @@ vector8_eq(const Vector8 v1, const Vector8 v2)
 static inline Vector32
 vector32_eq(const Vector32 v1, const Vector32 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_cmpeq_epi32(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_cmpeq_epi32(v1, v2);
 #elif defined(USE_NEON)
 	return vceqq_u32(v1, v2);
@@ -411,7 +444,9 @@ vector32_eq(const Vector32 v1, const Vector32 v2)
 static inline Vector8
 vector8_min(const Vector8 v1, const Vector8 v2)
 {
-#ifdef USE_SSE2
+#if defined(USE_AVX2)
+	return _mm256_min_epu8(v1, v2);
+#elif defined(USE_SSE2)
 	return _mm_min_epu8(v1, v2);
 #elif defined(USE_NEON)
 	return vminq_u8(v1, v2);
-- 
2.25.1


--3MwIy2ne0vdjdPXF
Content-Type: image/jpeg
Content-Disposition: attachment; filename="v6_x86.jpg"
Content-Transfer-Encoding: base64

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
obZUNsqG2VDbKhtlQ2yobZUNsqG2VDbKhtlQ2yobZUNsqG2VDbKhtlQ2yobZUNsqG2VDbKht
lQ2yobZUNsqG2VDbKhtlQ2yobZUNsqG2VDbKhtlQ2y8z79tb4fC8+QfhXmfftrfD4TeQfhXm
fftBwuBkZGR1IiHFY+WvC6aPNxGPlrwmaPNxGPlrwmfI3w+EzkH4V5n37OQdS01hwiFjzGoS
whRY8NnNtbBc2GBrITP4aXyH0flktE5nDLA6n8qyIsm5jVuVTbKptlU2yqbZVNsqm2VTbKpt
kes2yElU2yGYn4KptlU2yqbZVNsqm2VTbKptlU2yqbZVNsqm2VTbKptlU2yqbZVNsqm2VTbK
ptlU2yqbZVNsqm2VTbKptlU2yqbZVNsqm2VTbKptlU2yqbZVNsqm2TornNy1CWtPL3NmHflV
TbKptlU2yqbZeZ9+zkETB1hZaNBHdQFihwITHb2sAR5uGxk9eESmjzcNjJ68IlyN8PglB3I3
8EEIfIha/qtKZud+/R8z79tb4fBKHI3t2Bpk9/soRcZnP36HMw/mxMhJBu0c3Faa39X+ej5n
37a3w+CUORvbS5xkBrK0nSXfLhsIZZM8Ty45TOoBHTNK+YaRu5NKH0n0fM+/Z8ta018RwiOg
uyylshTfE53ExxzAEnBTfE53ExxzAEnBQocSLzgig9wEiFm4XTesNW9Vtuq23Vbbqtt1W26r
bdVtuq23WTgUOu26rbdNOISVbbqtt1W26rbdVtuq23Vbbqtt1W26rbdVtuq23Vbbqtt1W26r
bdVtuq23Vbbqtt1W26rbdVtuhBYeqzMy3ppxjVvVbbqtt1W26h6HDcJEzeU6FDLQMJGtSLgO
sVW26rbdMAP2ELv3qtt1W26jZiRb+yrbdVtuq23VbbrzPv2cyMj3KO2LGY9kaoCHLulvX8zF
5wYCwSbLX3+K/mYvODAWCTZa+/xTYkaNzhYJN6suRvh8IcjOzl5q2R9U6NE+ZEeDmoR/QOWe
uI6kIxovzomufciE8fr5OZZ8yJlL6JsPv2vHlH1b/jo+Z9+j9wf6/svuD/X9l9wf6/svuD/X
9kYoxMfJpkNYzC0cczGhTjCZJGdio+KHpkSUVwBhxpD/APpQdHMcwMetzn5gD6rRTjILooY/
A6XjqUBjYj3MizBa92KWU5/Hb4fBHKzs5iHOBC1fVRPL3UD+wche90mjvRj/ANGFTyx27nIu
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
utVu9FW70VbvRMm80a/IqNpcyG0hVu9FW70VbvRVu9EBHiuDcSAbHIDcpS/6X3g+YX3g+QXz
3cP/AEvnP4f+lDYx5cJ5zC+a6y+a6y+a6y+a6y+Y6yqd4FVu9FW70VbvRVu9FW70VbvRVu9F
W70VbvRVu9FW70VbvRVu9FW70VbvRVu9FW70XmfftrfDl3woQm4qLHdU9yjeCb/ZyOedTRNR
I7qojuXS27n/ALrm4YxRn6huU3/Nfm7oMI/+v91DZ3ymfHowYW8r5YXy18sea+U2y+UyymIb
bKlUhUqkLUFOXYvM+/bW+HId7sghozM48XWAocM6wM1H/sKheB5HaJCfhhtHXKbCbqby6biy
GKaiaa8fRnRgSGKQzC+6uX3Vy+6uX3Vy+6uTXvhlst/bvM+/bW+HJghZmcmhc7FOOMe/dyRh
+gqF5rmoXWjnu3JzonzX5noRobdtybDGQAWR6BM8mBb+SoLWu9Dt3mfftBDXYTLI7lGfo8WI
9sOE7G57pjF3SQMOK92KG6eN08wNagMESI8RAQ7G6ervXfZN16vyotbW5c/EB5x+rq6gtrhK
2uEpw62r8pQiHZmnaVFBMSIcuqtrhK2uEra4StrhKiRYTcUvovkT8QvkS8AsJaWDfJfLxfUh
ZwPRPfGY4E/RTk7hKzDuErU6xU5O4SpEHhKyDuEra4StrhK2uEra4StrhK2uEra4StrhK2uE
ra4StrhK2uEra4StrhK2uEra4StrhK2uEra4StrhK2uEra4StrhK2uEra4StrhK2uEra4Str
hK8z79oczEW4hKbda5sxoj4WHDgdhlLyC+0c+L1cAxnUEHmJEiFowtxnUORpP5UXf0GHow4b
6MWaykpucB5rqvafNa0STqUSKdbnfhXmfftsLQ4Vb5TQht8zvPRiGI6czlLuVb1m55WWJvgV
W9VPQY3UPwrzPv22LpLu7Ifi3mfftTnuMmtEyUGGHEhlwm3GNaLTqOSDYYkDn+LeZ9+1Roba
nNMlAeIcRghg4sbZZnuXfdN16vzLa4itriK2uIra4itriK2uIrW7WNora4itriK2uIra4itr
iK2uIra4igJu1fmK2uIra4itriK2uIra4itriK2uIp2btf5itriK2uIra4itriK2uIra4itr
iKaZu1fmK2uIra4itriK2uIra4itriK1u1jaK2uIra4itriK2uIra4itriK2uIoCbtX5itri
K2uIra4itriK2uIra4itriK8z7/8v0aE75b4nWG/IrSYTMobS2Q3ZdJzZkTGsKNpGht5uGyC
4Yhtnf8A9rRhAGEPa7FLv+vSABk55kHSp+qh6OCXQjpDsU+/IymtJhMyhtLZDdl0nQpQy1kI
ECIJjWV9rDm1sFuFhM5a5+yhFxmZa9/SfpGAYOf+ftgYvbuT9Ll/MCKet31yl0oTzOYisln+
oKKyM0OayG3CD9Z5qWkjnAyE3Di8Tn6KEXGZlr39Fjs5uhOnM/ULDBBMfGHPi/k+n/Sfpcv5
gRT1u+uUuiWkkT3FEgf1HtaP/IrSIbYpdjiw8bt4Mpp0KEA2GYWItGqc+14XiYWFgl3nvn0n
Mfm1wkVlj1Sk6I4ixKxMbnKWZJy6ZYWZF2LX3rCwS7z3z6QL25jKYJCaCyWEYRhMst2SDWiQ
GodLFg78UsRlPw1LnMHWni15T3y6QMTEZbnkJuJrshKeMzl4ps2UjCMJlluyQa0SA1Do86ec
x7+dd+6LhzgmcRlFdruucwdaeLXlPfLolpnI7jJHm8QnveT7qJNk+cq+qOAZnWSZn/lP/8QA
KxAAAQIEBAUFAQEBAAAAAAAAAQARITFh8UFR0fAgcYGhsRAwQJHBUOFg/9oACAEBAAE/If8A
k3FhLB2CI69BgQAftFPpVUBOAYxUTdiMTMSgGKJACbcdpgwDGNf+PMQTAcBM0TdhCCEO4ZhA
LHbMhmF2OJihKfoFgaDeSN7ebtM0GwnNE3JOZQ4pOSKWwrYVsK2FbCthWwrYVsK2FbCthWwr
YVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2Fb
CthWwrYVsK2FbCABoDAeZ83tfj+tO3RfN7X4/rTt0Xze1+P607dF8cHXEOGcJ5TBFJcu47lA
W8TcQAF4lR7AeEAIPMjOqCVBBwQAQWZfGqJ8WKs/EcC23eCunRXTorp0V06K6dFdOiunRXTo
rp0V06K6dEXAAqSeFdOiunRXTorp0V06K6dFdOiunRXTorp0V06K6dFdOiunRXTorp0V06K6
dFdOiunRXTorp0V06K6dFdOiunRXTorp0V06K6dFdOiunRXTorp0V06K6dFdOiunRXTorp0V
06J08/2fHG4mBixY/ayvRzA0QSxRIZ86EZDmAoEUKutuQ5gKJ4MQJZMgcwFB6dr8e2/ELfyi
dui+b2vx7ZO1MkDGB/KTt0Xze1+Pbijgv5/lTt0XxyIMg5oDNF8YaM2IPNNnRBVCH5FoIZBb
ksA7nJfmgFk+MgnHOS8Maena/HtPOQKMOZL8/wAqdui+O2wBbATAlFHQeZAMluURJvikGODS
DoRE7klwM5wG5IxYNhImUJOIQwqibknMocUnJFLYVsK2FbCthWwrYVsK2E7gy8wkQHLTKQks
k0FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCth
WwrYVsK2FbCthWwrYVsIAGgMB5nze1+PakIIABHtfyp26L5va/HtRkcz6dn/ACp26L5FU/I7
Io0AlAZ9I4JTCWc2RwCkoLObena/HsksCckNtn6dr/KnbovjimgGKBChGAQI9MtjUxzOSMAw
gFnF2V+syPMS5Ibo7IHwYq79QwLbd4q6dVdOqunVXTqrp1V06q6dVdOqAHmG3mhJeAGoV06o
YuAnJ5V06q6dVdOqunVXTqrp1V06q6dVdOqunVXTqrp1V06q6dVdOqunVXTqrp1V06q6dVdO
qunVXTqrp1V06q6dVdOqunVXTqrp1V06q6dVdOqunVXTqqjIMWAmpX4qPtXTqrp1V06q6dU6
ef7PjjLBMQOCE+RgEmE4yksO0ODsjBlMJ6jIwRTCXc29O1+PZ7dRX4ZenZa/xCELxzGLcEAB
QA4bU4Z26L5va/HtXtfTtfnEwmwRMYiiZWgJJjAhwDBSYZMAoJfuS6snu14Z26L5va/Htbt/
Ts/mjrBOTBEXi1hzc2qcFkDv6k5uzVFRcIpfTzeXcNeGdui+PEwwQdHZgDWpjcnKApejIHDN
hzQFL0ZA4ZsOaJ7ebtM8GwnNE2BORQYJGQKWwrYVsK2FbCthWwrYROCWQKCGXAEthHAECZdW
wrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCthWwrYVsK2FbCi7xkp/4RhOkSlsK2FbCkNgT
Abn9IAUiBmSOSgQieSthN6RCOTiRhdBWwrYRyzu8JJbCthWwrYRAvBcHzPjiQBMxEOAeSNDk
ZNG5CAuyUDZgMCICUS6QNmAwIgJRLoKwTbhvMmJcw9O1+PZl8123yZGIKGYg/i8cTbLqoRe3
qw2ArZp2sxsQNVUAMuQjPYekaHhgnm0QzAQuWfqO57Q4Z26L2RBBBBwBDwYhcH4mv8ENAMYR
E44AgYBkzPCauB2wdVN2xzQHmOncQ4iwQRInRwcBABMcO/v9r8ez3Sw+SmCyY4XTTlIn2JwU
u3oPFOEidASCYTOGvAZOOxxKJqMgJwtLg2JwcM7dF7JBwbczQL/iIGQN1AuiCOJCzmczRikx
qQfIDNROsxAhnDgppOuIQYsAAB7/AGvx7OFX17j40+c2ABT7iL7CaFB7hODl7+hueIpjE46K
QSAPXkAfKNhgeRI6Mw5nHgJgMwyZHhnbovZDEbMBDEoxlDDHIAgEELRIoMKoIWiRQYVQUosA
2MUpYREU9IHgz8zR6vaBgCckgEcFlnBmDA4RQOatyHERVPpmFFUA4iKj07X49hxqWQiQLDB/
Xv8A4U4ZcYn1wENMaqffjInv9GDWV9TdyBuwMjY0PJyw168BvmMeQJCuiYOWww9qdui4dudV
tzqtudVtzqgHYN5/sJ9pvTmOCsRBP+iDBIvXBBP+iDBIvXBBQyuAAR+wWBdEh3Z28n2v8vnk
iOo4d4BCEsDGKL9AHOAA6nKiCtJHBIABZF8KIHwYq79QwLbd4q6dVdOqunVXTqrp1V06q6dU
BAHyZ+q6dVdOqunVBEgvBv8ASunVXTqrp1V06q6dVdOqunVXTqrp1V06q6dVdOqunVXTqrp1
V06ow2D66TQhFAGQAB2irp1TaQAFpBzOO3QgpA4d2V06q6dUMy34/NQ2wBRdjzRXo+GO+A8o
VeBgHaq6dVdOqunVGzcAmTmoNwK+6unVXTqrp1V06qHR3Afqrp1V06q6dVdOqdPP9nze1+OM
lg5khJSQgODufgEgCSWAxROIlxI3mmLGQAYD0MYfPgO8fwcFYAdlKzaDMsGCHjphc5f6fbpj
8W/eGdui4dwLcC3AtwI2plMJEYlCrjnyzjAAaooowbIxCQxiM0UUYNkYhIYxGaKcxwcSQSA7
xREECZJkoVsLAZTh99kO2TVgAwkCHic0VxAhEIwHAcGLMUVxAhEIwHAcGLMUIvi5IyOBL84I
yzPAEkA4RkXOns9r8cZyyAIAw4O+9sfwNyOATpmI4Emx4MOomMBlqsLfqM1shJRGiTqwQRzM
RpIbrwVADIKZe0YBgN80AlmxkOE0LyGZUKIjRg4RCU4YoZHmjQBx12IWIX4RrakOeGdui4bY
rYrYrYgGDBO4RRGFzAwOaCIueTMAksYHNBEXPJmASWMDmghXmAZuKAKaAQCGI7yAKD8rCJAY
fXdHvk1ZAjGYBaIyRFMJkkIwWBYkMzBEUwmSQjBYFiQzMEATKbEMyAgtTIYAgDUH0PZ7X44g
EKQRsRS4e89uBeFw7w8qV4I25egdTzgiguRmTAzR82PJoPR4+Q+CjTyCZhn8sgBpB+nCMF1u
zHiz/rPjypJigcmOjDkHlRKX86fQJuxRGMKLcM7dF8jLMXNgiQuitJpogIyiQFL0ZA4ZsOaJ
7ebtM8GwnNFGI6TKEC0vEqexRU9iip7FFT2KKnsUT0c1F4QUDA2SVPYoqexRU9iip7FEwAgO
JBU9iip7FFT2KKnsUTIluSSQQ4cijA5qnsUVPYoqexRZRiEg1ViEAkUkgNzDnUKnsUTEwFJL
4Exl9faxyImCTqnsUVPYomyEJImEOMo+JtoCp7FFT2KKnsUVPYoqexRU9iip7FFT2KKnsUVP
YoqexREMCcswjDYCp7FFT2KKnsUVPYoqexRCcjoIqnsUVPYoqexRU9iiJ37nfIEq5fKKIoaE
XLAxJiXLclA2YDAiAlEugrBNuG8yYlzD07X44cCdAZlRhAOj8U3PwkcmXyrTQBpLBB6EPQhj
yZ5HHGD0sm8KfYMeQt59Of27v9WJtOamZhHLjunBym7sURdl3y4kdM0BBJXNJ8T7IvpA4lda
ZZCE0CIoFJRUnRA3KSlwHV8TvJBXJgo6h4Z26L5va/HCQ0iOQ3t0IDBgIR1PCIc0oOaMSCYC
AZ48Bwweos0+SYiZgZLn0nZ1WBLgMzgEa4BLkk2lEWQH1DJAwBMAMB6ojWMwdAj+BVhD64Gp
z902EIl6JfnDUlITirjCJCrvuntHzIqxU5GKAFJjHMggCSHiwMwEKYim4U4LAFhCAAwDDhnb
ovm9r8cDJT/ai0eJJZhgoRZme54ACgXI4ItQYzS23T1AePQc0V/KqcUE8OTgQOvlFEdRpQ08
+nVz5IyC2sP1CKYDADDg5zfzRAh1CMMfA78JVwAE2SbMQEg8Kv8Aut6Dqii1UYIuwN0GqKOC
EWfCwnbovkyOSlAMfpRjE8ggzMhifqagqCwG0YnMQMfTtfjgJk8w5I7ueAfAjB04baDgfR6F
t0UnAQULyPw9M4iMOqBgQEGZkhDLBMAMPTeeDRc8NzidOEOX/BRUWJYnaZ4WTlfr8RAzW3Ni
rFRJytEHMGycQCYCFAhIsELfBZ26L5DIwg0CzQ4aQgS5M5xefNECUSpYAefOSMRPIkIAx54y
gii4pg2iMgsMGGit2hW7QhdlxDRR5iPk06eUTXBOGiZFPCGMg2SW7QrdoRPhiDZhL7QY+zaE
X0rdoQxM5HT/ABW7QiADBgQZAfpVu0K3aFbtCjwgJEm0LCO6eLBzsAfat2hW7QrdoVu0IbKA
QQBP65oGSmZKFmAOQPxMQl5B/PUQgi2YQ3bmh/mNFZGitjRWRoiMT+jRCnxyBohBDEMtCt2h
W7QrdoVu0K3aFbtCt2hW7QrdoVu0K3aFbtCt2hW7QrdoVu0KCd8bq+b2vx6w3FsIWifDI1QP
l/pTC5u4XWW+PSeuS6IqrClyfXkB8oxmBQh9kRDiRMKcDymdndAMbfaInhNpzD41REGVkSp6
A5EhDGBvt6iq2HOaEuBWxUStiAC4+pYZfNvhTt0Xze1+PSCSqFE1GIAO2RAZvuDEpgVOy+ov
PpJ64CeejImhIGc4+pOJkj+zqod52jhscJjsEMyHW3Oi250W3Oi250W3OiB8wgA+dO3RfN7X
49AAT9CCxLcTj0a+lbB9l2L2KgPKgA91Edly8mnAcpmQaqUOQCONEThH2MTRNKI5orQlT0/1
BBGLJMiIqUxqJ/MPJAhkCcFAuAPnTt0XyHNiQZd2aGkYIGhmYiLtBFCcSJ4IpcpIVE8JGALH
ljKEUUYjpMoQLS8SeoYYOCGCKyeYbCqexRU9iib1EY6HJTpgmDMsnX6BJxYZ9VT2KKnsUVPY
oqexRUYpgLMijnqOqPOzmdU8GIAEjjCHIlB4HQ9UZwcA5Nd1dBMDYKIJ5kNxRQ8ezBO3c7MF
T2KKnsUVPYoqexRU9iip7FFT2KKnsUVPYoqexRU9iip7FFT2KKnsUVPYoqexRU9iip7FFT2K
KnsUVPYoqexRU9iip7FFT2KKnsUVPYoqexRU9iiJ37nfImMh0wclFehkEBsAoJ2Z4BF2YDAf
c1BYFA8pgMhEx9BEAAAEk8lXODmMB1UvUhwyjQyCBEBABIBN42ZQkxWgKg+0AIwPNH3Rf7/K
nbovmnVccHLALBuj3DhPRONhYrwNEE4Bk4W1B1W4GiDsfuGiGy2d/KnbovmEgCSWARhJxu/X
9adui+U4WDIAKCoLAbRicxAxQEXE4UU/yMcT/WnbovlSiyqKMRPIkIAx54ygijk9JhGFeXiV
fYqq+xVV9iqr7FVX2KqvsVQwkSDqc1X2KqvsVVfYqq+xVV9iqr7FVX2KojLBI/RVV9iqr7FV
X2KqvsVVfYqq+xVV9iqGRmAwcgVVfYqq+xVV9iqr7FVX2KqvsVVfYqgnHCBNxV9iqr7FVX2K
qvsVVfYqq+xVDCRIOpzVfYqq+xVV9iqr7FVX2KqvsVVfYqiMsEj9FVX2KqvsVVfYqq+xVV9i
qr7FVX2KoGbud/14olIGEwwB6jsgjO4cjMQGWfXiGcFICRiORRKEkaV1MxqdBKgg4IAILMvj
XiLG7o4Zi3NEJoYOhcDM5CCM7hyMxAZZ9eJ4CDbxcLAiMBHDqoUnbQOIkH5Iop2EdMAWB+uI
hhE+KEYQB+ksEeVJGEQxytBuInxBQkGI2awLSHDnhWAipNAMUCHrgiinYR0wBYH64SHiR0nK
IyCY7RqYg7xcocyPKkjCIY5Wg3CESUBLR+06naMGdoPRAwDsUEgDpNkOGqAg4Hav58ubuOIs
QcwRJE7MS4SSLMkxPEEJwA7OCjMJjAC5gJ4MQJZMgcwFBxxzGOJ43cGY6InZiXCSRZkmJ4jB
iIgWtk4MqJjCij9wIUQMATAkBxE6ZOXojmji6Imf51+wZ6txDxhGAeBcFgRFCgiFiWQucjmh
gNzR+4EKIGAJgSA4W6TIAM8WoogMlKwQSiS0CJn+dfsGercOdHHz+xFQFxgBzeRFQjA1pyBg
n+cTTNlEx/6n/9oADAMBAAIAAwAAABDDDzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
yzwyQwwwwwwwwwwwwwwwwwwwwwwwwwxTzzzzzzzzzzzzzxTzzzzzzzzzzzzzzzzzzzzzzzzz
xTzzzzzzzzzzxhgyQwwww00wwwwwwwwwwwwwwwwwwwxTzzzzzzzzzzzzzxTzzzy3Xzzzzzzz
zzzzzzzzzzzzxTzzzzzzzzzzxxDhjDDDA1HDDDDDDDDDDDDDDDDDDDBTzzzzzzzzzzzzzxTz
zzzVXzzzzzzzzzzzzzzzzzzzxTzzzzzzzzzzzBjhjDDCftHDDDDDDDDDDDDDDDDDjDBTzzzz
zzzzzzwzzxTzzzn5XzzzzzzzzzzzzzzPztfzxTzzzzzzzzzzzDzBjDDCXhHDDDDDDDDDDBzD
S7RHXDBTzzzzzzzzzzzzzxTzzzxVXzzzzzzzzzygLy33l33zxTyw0oDTTzzzzzjRTzzzLxXz
zzzzzzz/AIdJ688+8s88U87ygAU088888sMkMMKXMFMMMMMMMFGOMulMcPMeMMU888+08w08
08888U88ld8V8888+2xH+V86c86gXb88U8vMNMMsMc88s0MkMIXMMFMKmGWuNMNMMOFB8GJD
MMU88888888888888U87W8xezW+288f801xF/wDD3LvPPFPPPPPPPPPPPPPGGNy2MHlKcM8M
H/NjRIy8MMMMMMMFPPPPPPPPPPPPPPFJuvKfCvvJsshLDzrzHfPPPPPPPFPPPPPPPPPPPOMO
Hw8UEsCw9PzzEcMMMMMMMMMMMMMFPPPPPPPPPPPPHHK3LABx1fPPPPPPPPPPPPPPPPPPPFPP
PPPPPPPPPPOMHfPPPPPPPPPPPPPPPPPPPPPPPPPFPPPPPPPPPPPPPLLDDDDDDDDDDDDDDDDD
DDDDDDDDDHPPPPPPPPPPPPPKHPPAHPPOHPPDHPPEPPLGHPKMPPCKPPPPPPPPPPPPPPPPPPPP
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP/EACsRAQACAAQDCAMBAQEAAAAAAAEAESExQbEQ
YXEwQFGBkaHB8CBQ0fFw4f/aAAgBAwEBPxD/AIMiAgCeD9ThT0xm0fqTE5u02j9TvpsH6gWh
CY6TJ0dyQOt7tiPWO1z2PbhehwNlB5ub2y7t7iY1Pa9oRmnDGtZR3mjfAY7Iy/szPPu6c57f
57NQZjDpeBvFqZtkZf2b+I1mO0AUTKuZ+OkE+0Jrh25i7lw9v89jYw7k9AJPj8zDaM3pArAm
Feu0F165dPzsvYrfpOQ+nKACHkTft8DweG537CpWZyiMN5bQGeRlwYC+DCorN204AQWABRwv
WVHz09/xyRc5iZIrtgFrTeKrbw3O/wCONa6RpCtGEq6R71X2JZORK/gQMcNeNCyhXofairGv
RX5aS9L0n+QSLJm1o3p+IjJnOinN7U0VrEsf6S+O734Y1rpADmh5f5A2ctP7GDfx+Ib1n6cB
Z8de18UaKwRoXoxD+TEFDQPHxdpjYMYozIE7oq54y6tXyPeYl4jY4MNRjVPOWnA2MtP7FYaL
vBWY3nwVUa17wKK4WmIYYCxCj0mYtWl5d+aUH/sVKfWZiw/VmLo+cwKuBazPe55wK1AbKK6p
n6sd+M2iAbYxea/am43ly8W3DN+3jxTJeQXGYmY6M/xmGkopzHQIFS90sKphPMvvIsZr6lYv
wc5irwit/rUHBZW9eA7DFfIXDIcCrgcrQvpbOUfTnOUfTnKtyCtOq8ph1gdWc36sQtHznNnO
irn3i5jgZvI1YglQ8g/rmsFjEb40vlSwyHAQIlwFBrMz8ZqIHUdpoCc6bSjLYRhk0ePVmLHE
95YwpY4GQK78puRrvXy+XExs1cL6ayjDzsm9w119dYGQnLoYHt+kQV/yX//EACsRAQABAgMG
BgMBAQAAAAAAAAERADEhQWEQUXGRsfAwQIGhwdEgUOFw8f/aAAgBAgEBPxD/AAYltBNMGxl+
pSJuw9LvtRAJ+psAZTeqjoV3XD9TifcD2X5q/i9D9RI7hVytT2Cvceh5IZ5DyzhNHpRebgvF
Cu70PHUSWmNikKGxgI538sofV0oQOh0rttDxEdz2YRlnUjFhI2PB3X+qtdPLOOFQgFe3dDwx
TYL/AFTyt70oJwKs12/1XQ0RNg60iVVw0fxzQrvrQ1jx1MphjkY5+hs9ydHgolPUXg73e18v
isTnscaWcWsQ8OtOIcr8fzjsblWt5lNWfU8ZQJaxsl4cDA+dnveh4EmbC9EURfrSEbt9hUDe
UgQW6s9iNQmlVl2NFmQ/GKilvYr/AKxTkC8GfGlDjfpmfW1GrBs91+JhGWdEBMCFMgZ0YLJ7
tRzVqf3thkjFttSW5wOLUFpehWDe5lQOYgzz/k/ja48a0vIq3h6eKAVYpbzs0Fj5dvZabMIy
z4UwLP5SYf8AaZBvn9elBEd3zQOQduxQt2feNswIKwyGHWlIBU0AJTOBOXH0ofBapwq3zwop
PNeQx5SQni8N3rWEGr1dhLJdxQDO0NQDSIt8/qhA2Q6UuLCLabDImUvKlll2SSCWb0jl51pa
FbIC9+1aXlQEKgQI4aVkab6crVl44YdIqYRi5uLz8mCvKoOJjeA94UIju6lNAUbFl3NPf0ag
SjDrjscDulw2lmB+KwJjzK0fOpRYASnGgsTnFJY+4UgMgDmz8eZQy136rBG+hA900MdeHLYE
XZ62HCnZbJx4l7tazyfqtZ5P1RFpMDEoGx5FB2HIpchPIrR8igLHkVZEeYXIu240CXW7e/Rl
ShGgE4CjVwp2WxEgIJYUnO18AoriNST2R60uSRxHt6UWkiUyzJ6dKB4OoyekE1j+ARM8XClS
Mhw188CRwffwbcBjxRxyrPjz/laLn/KQ3f8ASG4t/kv/xAArEAEAAQIFAgUFAQEBAAAAAAAB
EQAhMUFRwfBhcRAggZGhMEBgsfFQ0eH/2gAIAQEAAT8Q/E8aiQZWMg1pZkEZFDYhGBSTGrDA
soyQ3iMRIsk0Whk44IJgKIEje9qbMiZRcGIGCFm9vw9sF0wULBUJcLodaevFIkBAGW4MSNKn
JigFxAAtu7C7UWqcOLDK2QswWS1IsYDgGLYJKYLJalL3iBadjCqFWDpXCtq4VtXCtq4VtXCt
q4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4
VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4VtXCtq4Vt
XCtq4VtXCtq4VtXCtq4VtXCtqZgAgEAfenC6PxOpwuj8TqcLo/DamPVJMJOY61fGUEzKIqze
XGpvFPkxVihkQImF6HsCbfUgK873Xp8NM20sJC69970JmUVV7So0zsVYpBl9SVKlSpUqVKlS
pU9RDegRmN3x/lFSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVmcAuQZB1Pt
2iLZuJIYCI9Rkq9XKENCX0gCUbBV4agFniLD2wC1TMFFFLiDdlBYtSy6YrNN+vBgFjTw4XR9
OD2GIO7amIc5e8P4PU4XR9NjnjtvRYYAHsfg9ThdH09XnB3/ALNGLnD8BqRaVMsSyJyqN6gN
DFMRJkwDGBTbrTSGdAEyMIc4Dg8dY5YwCwIwwvReGBDArASYEdF/DhdH0gawRpDrg1D/ANmu
Pp+A1GgPahAWFBQnOHs0nTYAraII4mSyIaeYwKfG9gGAhOJQRGOucsIAsJxb1Y+IjggCEGBO
K9qUveIFp2MKoVYOlcK2rhW1cK2rhW1cK2rhW1cK2rhW1cK2pergACU525UCrFIXHlbyaVwr
auFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrau
FbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwrauFbVwramYA
IBAH3pwuj6SgddjR6IRXJ6P4PU4XR9LuF2/74cHo/gVTHb0VBuwNkks61jKqi7xMetN3iCo+
4x6038oZH3GPXw4XR9EncBLTWUCKPc8OP0fwGoGMqCYsl6f69BoEiCJwoKh9gtCe491YbVJr
vsnC4Ze4mKn7MSMzjs5zmM6U3KIq94U6J2ItQgz+jKlSpUqVKlWlAcgxbVLZBIyBbSTwKgBb
sCBjl/1/iFSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVYDBEErgdfwS03nA0ESFhYv
16+UqVKlYnIblCBdD7dO+CSRCI2RMqXbrmaCFRcJiUSEBBCOJIGnBBLUWsBPrTvglqPWAn18
OF0fRcd/T2Ox6puf43kTIlrSQ36pDoKUQADBkSP8JU4XR9Fx2t6EcmPhzO/3w5SVmBYGmR6u
lM8shlASvQPJmmChbCnVwPVypjitjN5ToYHvnXRaA6H+FqnC6PoqOrH7KEdv4fCft+4UCVjy
ppczADFqEiB8R8C60UrlnN38SSjvYkFl6AT/AGiBD6ITFMmLBlnfCuTif7xU6yWnOUxlUSQl
MNwL3OKsZ0mknHA7SX3EkkF6TSTjgdpL7iSSC9E9YswMGXkIkkF6UneIBo2MKIEYOtcK3rhW
9cK3rhW9cK3rhW9cK3rhW9KHCWa0eQERNrd64VvRPosFC+bXCt64VvXCt64VvXCt64VvXCt6
4VvXCt64VvXCt64VvXCt64VvXCt64VvXCt64VvXCt64VvXCt64VvXCt6CVgSQfAk0PuulTs4
pMyh1rhW9cK3rhW9QSiAYMQXCxMNdVFYuUzKu6q0dCaGMQs+9cK3pMqIXV/7VPQKaGGXHJAO
gtcK3rhW9RLwhHKXx7jXCt64VvXCt64VvTMCERkT7caFGWeLLIknKSdSnDiWG5OIAtxvnUhB
ONgFMAAkgL2vUhBONgFMAAkgL2vQ3m2JZMWICSC7a/hwuj6LjqH9NCC0H68P0P39ut4Fz8Vv
Qxf5SdimLGsT1VLuaV/SsPjDEkNzzLoSd7FCPKNchmHqbvoZV1O/cVJ/6g/5+HdiJCso64O7
pQwVq52N84wOgePM5h/RVHDyqKKKIedrvAbM3clD1GSJK5TOMJFqumEhYwqGKTbDyrAqS2Vl
Jjb1E/iMQgwyZEOCNOYe8Jh0YhFSzT6/C6Pou1zZoIBoeHyT9/bKBKwVmIE4k27yFdAGpP8A
yDeukHsgbeB+PlsBUeMgRAaNVn0g+PaPb0TauluoASvxStLq6jLydiDSHPyck3PrVRQ+NICA
RImIypxNLASSAyNrtO9SXuwl5hMj1puH4zJwAAlS2u0TWExLPBXImZlDC8iU0f5ohNCCUJYx
+vwuj6LEzXx/Ffv7Y3YDGLMsGbgdXpQiQU5niHoQeldIPYNtXQL4kbeAxbaSW9UsNLtCmCAw
H/Xr4+n8en/ykHkWdSx0mJdAdaaYEEMSXXqq+QE8QhQdHH61SOGgaVSAg4YtqGOS52spuYoF
PaF5l1iBfig609oXmXWIF+KDrQ4BetzggDchkuXo8q39JDtEXxZgiGJJ+ijn7MAJVcgKEjEI
g8WAEkpJenpCiiBxQobWSXL0nusOyRbryYhc18OF0fQgQwlu2r1jSGkePwP7PspMzfCPjidA
x0JGd2AzfWjuA3ASfMLaGWvh0C9sTtUyzD2q2WlkXNW+Seg0pJczmP5S+Qmt89Ef01co5Mmc
doD3+zqBhhhrKQylYk75JqFhRIhN3dQuzarxNiIBIdJq74Wq8TYiASHSau+FqbUbKPmRuEk6
Q6VaQQENghLIfO/SZgGIQpJ4C7OhfSnaKazuMIkoY20vZmZmcrI0gN33WozBIW1sJC699rUp
uURV7wp0TsRahBn9CVKlSpUqVYrqSSGV7p+PIVKlQSsuIBcxJT8fYlSpUqVKlSpUqVKlSpUr
JQ43yshrW1F8SFFDIDR1XZ8Cs+CSbMQZaBRgnyhMZzEXPqz18SpVQtSAxb6oKI0qxOMNaA+N
QDi83TJ7woSDDQAEB5JUqVE4gnNMnJgJNXBKg/IWcrFXWsvr5SpUqVhlxeol8y3B8pUqVKxO
Q3KEC6H3vC6PODpAJWiTuQvOT5Mdzc+wMsCVMAa0ul1tJa97o0L4t2JFnQAxD28AfNSgB9Cn
uZeT+mYqcsZUcT3RQqZ0JK96z7j2D6eqoI6WL5X0qnX+6uv91df7q6/3VZPi5RlmTA9sYYhI
WohrAjGIybJanh4Z4aygz3ZWp4eGeGsoM92VqgOhsAeRKKpMWLM2NpMwwNVatmv0BZBleZ4q
lG7cfjQkTBENQ6Ylw9ImoIgmZiodMS4ekTUEQTMxQYLSSAIoCjcQxmgY8hMWeGEsYgyln6HC
6POJeqT2rAGGOr5MT1/s+mlNuFBdaSpeoZkITjKTN4v08iuSha2NJytfotm0P8hmCFcVyxBR
hc7dtUTQUXWR8kpdAehdDB2svkdTv3FBBs6oB0gHuiinghwAgPLYMFjxygogCcCBs7ivcxqB
NJsU+iDUgZ0Y/E0FJHQXs3pAVLVnVixTeVY7cl/RH0qn8FX8FX8FX8FQAAAQBlUK8gpDZIsD
ZMFygwaY6azgz2ZXoMGmOms4M9mV6I6d6IQTCMZtODF4c8ke0Mg7hRJc2EY2BYx+qpQu3n40
IFxTLUHqZsPUhEVSTMTUHqZsPUhEVSTMTTia9LkoYFnBgmylXqEaBwhY+0W+jwujzMzAS0SN
1B05h5cb1/s+nBUxZBDcenygZVg1gZsOV1VS+AoKIolcxwpSwhmVMQNVseulXjEKzddRvE4u
fhqP7d/86vPslkD9lYEbvM4BPlHpWbi9ctDA81j5J73D6CgAAACwFAA1zDg9JpAoZ3PeKSSE
5An7pU9SISPZJU1AGFbp17/e1Aw4nWRD1Sh099cUsgQnCM2hpNJOOB2kvuJJIL0T1izAwZeQ
iSQXpWwmhe4RT8nBYUwMw81SpUqVADRWcd8J64UZIEHllSpUqKoZF6ZhJfy1KlSo9LUxAPSi
qwRxuiyb2xtOnkqVKiKUctJWYpeMusdaatRKR0T0Z9V6UcRgMk3MKJfCpIs9ljYoTEGWllNk
QAgmLsWDI64eFSoDCZgLFMUjOo+DKJEKtEonu/UqVKlSpUqVKlSoAqn7qZyGLeYqVKlSpP5E
glMcwJMM/LUqVKhOJhUkh7D9ww4CjCgjHo1H6n0AJ4CAQBdipCCcbAKYABJAXtehvNsSyYsQ
EkF21/DhdHlvOHcDA96SSbC9SQ6W83zO3ll+pkuAvD0F3WDGnfGjEnhl3as2HT7auVLd0qzG
x5DD3QutWaDG0GCdMmsnLw5GXlJ/5TDHQHVYPWsB/qLE7jpIB06vJM838VvV9MlewHsB6+aW
hsZkdMan0/QimrJrL9aEgY1nQhWaOyUEtkrRFsTWoXKwFMYP180z6oNkqCmME1/BUAChHBKZ
n1Grun7u/wDhVOF0eVVD1mZ+xbvQSQ4GQD5WTTY5LzjQJfTrRI0qWIDBbCPIrUgi/UjQ/cGd
AjmAqZl1W717VyuXUjlS7uPb1Gky8MulZTAleBeyt6YBi8ZeJXtjlegcCOgCwHh6bnsm9SCu
YM5H8+5Vi4e6AT8z5FGJgewO1WPJ+4wt/Z9fLc+HvCLfMUgmgQzkZPT5rju9CDOZEey1yTam
xn6B+quupGf1TNh0i1O2YYUvQYgG0UvmPVS1jHL+NacHJZaLiDII/wAKpwujyEyMIc3n6Y+l
QNNLyGxXNme70pwuHAu/kQinCgVkHNhJId8Fdg6QT+Mt/BgEsGNtwZtwOrSu8CS5cQvYR6qj
+xL65Z7fKTl4dFp6n/s0rrKqHcf3oQZBkACAPJ7u3q6SnkHcBRD2m8pQw1CTVbOjkBxAg6Wp
FxOOlQd89CPcVHyamPhoaBsmCaTprAhAzkuhUwkIX7wT8/5VQmYETZJgMysGYLNsRBjYboFG
0CCKouwpw5DiOJbAlyC7Dw4XR5J1DLK3Q3nuwdhovFFNkoLWbx0Fzq5m5D38cKVKI4JDj1uW
ZxPY0dH0Td1VZe9Taue//Hw14DoGb8VeKwZDIgcAQJcce9H2AfACwB4dFjrawCGgJ/LUPjGP
dtqnlTY4ld4WDyxSwl+8vigAcqQWEspjegiPcoZLmKY10vOB80dMMAAKABMRhq1DtSfdn/Jq
IIRIEaOmXWl45wE5glqUuEs6kITgUCwMUyJsL3qFeTkAwDWWVhe9KwmhH5TT0GVBCWLX8RAg
iqs4xuLaViakDpGCDrYmAw3uo7w/Czciq0/OVTojaJWsXHxIEGQApAGoBIzel2j1HaYLdumN
CfAglvjMEYifAQR4IIkRJhGOV5CBAgI8e4IOlEdlS7AryCMotMt5RAgQISxfNbUiDS0IO2EE
BABc0/Rmk+7GpZHUX3I1/FU/lKXdPYXECLJs0XYKNfB60yZu3g9bhFaG7WIiTcE9wfugkQBB
Y+xECBAgQIECBAgQIECBAgVRSBVi5lvveF0eIeLkGA9hgPVoZBUSLpcp3T2roUfY21SP/ADb
w+BZILtQry+8ui6+vsPHTNkejsUeNsausIzZwM4rFGYUoyl6q9V08iSTNpmIJ6jFWkQDnmHq
x6eVWGVd0DR4m6AewxQYQGf6hq8ZZpXy1h5XRX8BQULuASViXokV/AUCz7pX8FQQJMGFXdz/
AM8qcLo8JLFnF74voT8UulYSVCz1SAdV7nSkyMwzczutdDPYJ2qxZrvf/nwUviwCIui7ClwJ
Gotx2IplXuq+JvU45A9FtE3REW7QJ1Xy27sZUKcEDjCeYMMMMMN7xSIJzQzV/wBupwuilglw
phURCTIYbSxKuWNqQInJITjdddV8eH9Fh1EeVC7hdDjcFM9Hu6iVPeZTiSzZVXV6T5EjQ4ao
Seks+lNICiJe7Gay03naESuoe9BpGyyUVYrgwirf2kbTZ+37UtXsAxooyfQilUMvZWMNwGLQ
QSOaFqiSxMZMH/v+tUV0MBkFoNmMYoO/gm5gmJFixE4RmNxMAYmxmRFxa1SzyUhGQ6SQsWtS
thNC9win5OCwpgZhRiT5Tpabh1+dKEtcyWW9kMcXaOvjUqfIOqVIzsGcWcHqtRnyHKW4QtL4
DXy1KlSoiosRqQLktF5pyROqoODDRUkzWCoPSX2prPWKJOlz5Jo8BTBE0DUOJ9VbDqe1T4nI
QFLCrJJigiFOr/zoOimAoUXIqoVSQiVPvlSpUqVKlSpUqVKlSpUqVKlSpUqVKlSpUqVCcTCp
JD2H7g3RKgCkSkYdGKlJSWlWNE60papgFDGBAFTZjTviLMUSXBbklmPgmlwIABK05xRKFj+S
HoTQAAACwHiCLBIaBSALsCSDPeAoohAgAMgpoB5IPmmA5iOf3X8lQJbFZgFBXgJ7Xfn4fg9R
z0DrBBI5Ti9DrRqiiyhTFbdA8s9896lW8jdk9vBQaAM5D7E0WXx1J9jX9WgYVmZBNG7SIlKq
yq/g1QywJVwCsdA1M0g9iHqfiFQ2bjYAlfYpw5DiOJbAlyC7CpP9yolEJbpSzDAKZAm76e34
hUhLTtYILC9W3rUK8nIBgGssrC96VkLoXsMU/J1WBMDIfPUqVKlSoeIvSCR9j9CpUqVKlSoc
sljMGPyfoVKlSpUqVHwA8gJvkv0KlSpUqVKj1jxiKHnVKlSpUqHiL0gkfY/QqVKlSpUqHLJY
zBj8n6FSpUqVKlQnEwCSy9x/LySloY6u4QYzoW/CHTSZLECxLXzQklrtInITXKr5NAJrCYgo
K6skvT4aZtpYSF1773818QADRIAvBgZoMJRGZ62aw5Co4pei34Q6aTJYgWJa+ZQRRhcDKFfg
sNFJMTSoJSsgY4C96kZSXlAhzUDOc+YxrcTiMFwI4OebZGRNy3wRxIzLRwv5pYl7DM2QwtLg
YRLSw1b6wJ2ULkVD4SbBRCc4Oe2N6kZSXlAhzUDOc+Vc2YLCMGwS2IxXFaQSBrDL75gbBJgh
RkTct8EcSMy0cL+U5ByCPOBc7l6nn4jQUoJQYroOLFTqkZSgi0FUMhgsUfkPoEgwCJGMYafd
2A8xIrBAUaiNNDXQlxSUsXVwPMt0Z3AQkiJbRqYTNOtkIoCLYUsumKzTfrwYBY088o4qBCkI
yTmiMqaGuhLikpYurgeaehEU8UJVGK1YhkmzWZFuKTpQPfkwAgAyA82EFmk2Z3Eryym9XEEE
XZwV7bqdfM5GkKrYDQbjE2L2KXotDmMpr1kkqBhuNmsyLcUnSge/JgBABkB5Zvk0QIhAICQs
IsWtQyLh2JAxHpVxBBF2cFe26nXyq2IpWKzZwYZNLwyTLxkQIVVUJZaLF7FUgWJtAGEYTjSr
MlsnAuoJYJgl/Kf/2Q==

--3MwIy2ne0vdjdPXF
Content-Type: image/jpeg
Content-Disposition: attachment; filename="v6_x86_extended.jpg"
Content-Transfer-Encoding: base64

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
Lw4xjGsIMMOJDLhNuMa/Y3l8MkmQCPZ+yZMqiKTc3b3cfbhb44nBaTtR/pUhkPZJomvGVkPP
s5+nmHPwl0hOTdZUXEIn4h8MhoMNwa36ZoCHCezDDdPG2WZGpAQ4T2YYbp42yzI1KA8Q4jBD
BxY2yzO5b+qbr1fMqriqriqriqriqriqriqriqriqrii+I4gf9iqofZR9dawQwQOaquKxOcQ
P+xWDs+IN3uJK3l3zLMm4qTMR/qK8ZWU+qquKquKquKquKquKquKquKquKquKquKquKquKqu
KquKquKquKquKquKquKquKquKquKquKquKquKquKquKquKquKquKquKquKZr18fp55vL4M35
u3N4oR+15Q6WIBokB7JbT+AWk7QZN3NUhJoUmBTeVl+hs5+nnm8ke/gZ4oztTV+I7WccU7ju
9k3ukpQRgZ8yxO8TuJUmBTeVl+is5+nnm8ke9o4fiju1DgtPH8Ud32U3GQWDszJn5lj7S/G/
gpQ2yCm8zWX6Ozn6eYmVFf4mNhHxYhLdNe8Y+F4cYxjWF7xj4XhxjGNYQYYcSGXCbcY1+xvL
uRXtIeYeRAO9dlOyWnOXGa0MHxR3buC08c4ox1k7lhhDSOWLtL/6AsLGhjVnn+lM5+nmCeHB
duhNhxQ6KSWYoZAPhCAhwnsww3TxtlmRqQEOE9mGG6eNssyNSgPEOIwQwcWNsszuW/qgc9XH
2VXFOc95yGrEnD5jMovhscZEDJYnTjdpdvB1L3riG/KshhW/qqriqriqriqriqriqriqriqr
iqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqriqr
iqriqriqriqriqriqriqriqriqriqriqrima9fH6eeHJBYtpxMgJrHhlOQAXaGvDsENrs03s
7ARN0yeK4LIfpzOfp56BDDpEiajMaMgMLSOKc6hmv912ONLW6bv2KiiG/wAHD6JjXDV+oM5+
nnsqAGpjTqJzUWG5u3L7KG5z8pbJCMv1FnP08257jJrRMlBhhxIZcJtxjWnxPlE1pmNxPL/s
i6IzRiVKkJnn+ps5+nm40Nu05pkoDxDiMEMHFjbLM7lv6puvV8yquKquKquKquKquKquKquK
quKaZu1fMVVcVVcVVcVVcVVcVVcVVcU4zdq+Yqq4qq4qq4qq4qq4qq4qq4qq4pxm7V8xVVxV
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
JgYsWPynBtzRNDVBLGiKFOtuw5oIRQp1t2HNBCeDFCWTYHNBA8O9HHz+RVCsBOQ1Xq91UcTD
Bm9xWiGHJ7Kkun+cXTNtU1/6j//aAAwDAQACAAMAAAAQ4w08888888888888888888888888
88888888888888s8sYMMMMMMMMMMMMMMMMMMMMMMMMMMU88888888888888o888888888888
88888888/k8888U88888888888skYkwwwwwwwwwwwwwwwwwww1SwwwwwU88888888888888o
888888888888888888hn888888U88888888888440o888888888888888885F8888888U888
888888888csYMMMMMMMMMMMMMMPaMuMMMGMMMMU88888888888888o88888888888888lc88
88xs8888U888888888884EYkwwwwwwwwwwww8Swww3hAw6wwwwU88888888888888o888888
88888+v8882v81AP8888U888888888888w0o888888888/W+88yLe+Id888888U8sNKA0088
888csYMMMMMMMMI+sMNRNLO+MMMMMMMMU87ygAU08888888o88888888Zf8AOTPN3PvPPPPP
PPPFPPPPtPMNPNPKBGJMMMMMMMKcM7EMKscMMMMMMMMMMFPLzDTDLDHPPPPPKPPPPPPM1+5H
YlPPPPPPPPPPPPPFPPPPPPPPPPPOPOKPPPPPOtuMmhH/ADzzzzzzzzzzzzxTzzzzzzzzzzzz
zxgQwww61oLD4wwwwwwwwwwwwwwwxTzzzzzzzzzzzzzyxTzyBF4szzzzzzzzzzzzzzzzzzxT
zzzzzzzzzzyxyzdHW5hiPDDDDDDDDDDDDDDDDDDDBTzzzzzzzzzzzzzy0295/wA888888888
888888888888U8888888888888wgzf8APPPPPPPPPPPPPPPPPPPPPPPFPPPPPPPPPPPPPPHD
DDDDDDDDDDDDDDDDDDDDDDDDDHPPPPPPPPPPPPPPBPPPEJPPLEPPPFNPPOMHPLGJPPHMFPPP
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP/EACsRAQABAgMFCQEBAQAA
AAAAAAEAETEhQVFQYXGxwRAgQIGRodHh8DBgcP/aAAgBAwEBPxD/AFigVYIw4avTZFVQVdIJ
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

--3MwIy2ne0vdjdPXF--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v7 2/3] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml             | 125 +++++++++++++++
 src/backend/catalog/system_views.sql     |  10 ++
 src/backend/utils/activity/pgstat_lock.c |   8 +
 src/backend/utils/adt/pgstatfuncs.c      |  40 +++++
 src/include/catalog/pg_proc.dat          |   9 ++
 src/include/pgstat.h                     |   1 +
 src/test/isolation/expected/stats.out    | 189 +++++++++++++++++++++++
 src/test/isolation/expected/stats_1.out  | 189 +++++++++++++++++++++++
 src/test/isolation/specs/stats.spec      |  95 ++++++++++++
 src/test/regress/expected/rules.out      |   7 +
 src/test/regress/expected/stats.out      |  48 ++++++
 src/test/regress/sql/stats.sql           |  36 +++++
 12 files changed, 757 insertions(+)
  16.7% doc/src/sgml/
   5.0% src/backend/utils/adt/
  53.6% src/test/isolation/expected/
  11.1% src/test/isolation/specs/
   5.6% src/test/regress/expected/
   3.9% src/test/regress/sql/
   3.7% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b77d189a500..cfcb8189418 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,116 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timed_waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-log-lock-waits"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-log-lock-waits"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5314,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..dcf12a202e0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,16 @@ 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.waits,
+            l.timed_waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 b410f376d49..451fba4f8d9 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 b1df96e7b0b..3b7cbdb82be 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1737,6 +1737,43 @@ 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	6
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->timed_waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1921,6 +1958,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);
@@ -1938,6 +1976,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 dac40992cbc..3cd497a8e1f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6027,6 +6027,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o}',
+  proargnames => '{locktype,waits,timed_waits,wait_time,fastpath_exceeded,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 9c4508d5544..a4cc006794d 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -641,6 +641,7 @@ extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_waits(uint8 locktag_type);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_timed_wait(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index cfad309ccf3..86d70a0705c 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -3751,3 +3751,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |f       |f       
+(1 row)
+
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index e1d937784cb..4e4c68b4525 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -3775,3 +3775,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |f       |f       
+(1 row)
+
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index da16710da0f..d4b53545198 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -130,6 +130,14 @@ step s1_slru_check_stats {
   WHERE before.stat = 'blks_zeroed';
 	}
 
+# Lock stats steps
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.5); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 session s2
 setup { SET stats_fetch_consistency = 'none'; }
@@ -164,6 +172,16 @@ step s2_big_notify { SELECT pg_notify('stats_test_use',
                 repeat(i::text, current_setting('block_size')::int / 2)) FROM generate_series(1, 3) g(i);
                 }
 
+# Lock stats steps
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_unset_log_lock_waits { SET log_lock_waits = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 ######################
 # Function stats tests
@@ -765,3 +783,80 @@ permutation
   s1_clear_snapshot
   s1_func_stats
   s1_commit
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure log_lock_waits behaves correctly
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_unset_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 78a37d9fc8f..2fcb48df6c9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,13 @@ 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,
+    waits,
+    timed_waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, timed_waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index cd00f35bf7a..93de87747d4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1910,4 +1910,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 8768e0f27fd..db9948eedb8 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -944,4 +944,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--gJ14Jcshhoq1cFBD
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v7-0003-Introduce-a-new-track_lock_timing-GUC.patch"



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v8 2/3] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml             | 113 ++++++++++++++
 src/backend/catalog/system_views.sql     |   9 ++
 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/stats.out    | 189 +++++++++++++++++++++++
 src/test/isolation/expected/stats_1.out  | 189 +++++++++++++++++++++++
 src/test/isolation/specs/stats.spec      |  95 ++++++++++++
 src/test/regress/expected/rules.out      |   6 +
 src/test/regress/expected/stats.out      |  48 ++++++
 src/test/regress/sql/stats.sql           |  36 +++++
 12 files changed, 742 insertions(+)
  15.9% doc/src/sgml/
   5.0% src/backend/utils/adt/
  54.1% src/test/isolation/expected/
  11.3% src/test/isolation/specs/
   5.7% src/test/regress/expected/
   4.0% src/test/regress/sql/
   3.6% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b77d189a500..3a196bc305c 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,104 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-log-lock-waits"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-log-lock-waits"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5302,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..22efeb713e4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,15 @@ 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.waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 91a53a6013a..954fa51c3e0 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 b1df96e7b0b..27ca2d8f07c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1737,6 +1737,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	5
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1921,6 +1957,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);
@@ -1938,6 +1975,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 dac40992cbc..daeb45704c6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6027,6 +6027,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{locktype,waits,wait_time,fastpath_exceeded,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 f563132bf7b..1c261e2c7a8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -644,6 +644,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_waits(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index cfad309ccf3..3cae3052e40 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -3751,3 +3751,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+f       |f       
+(1 row)
+
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index e1d937784cb..ea4fd97a9a5 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -3775,3 +3775,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+f       |f       
+(1 row)
+
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index da16710da0f..42be68c545f 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -130,6 +130,14 @@ step s1_slru_check_stats {
   WHERE before.stat = 'blks_zeroed';
 	}
 
+# Lock stats steps
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.5); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 session s2
 setup { SET stats_fetch_consistency = 'none'; }
@@ -164,6 +172,16 @@ step s2_big_notify { SELECT pg_notify('stats_test_use',
                 repeat(i::text, current_setting('block_size')::int / 2)) FROM generate_series(1, 3) g(i);
                 }
 
+# Lock stats steps
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_unset_log_lock_waits { SET log_lock_waits = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 ######################
 # Function stats tests
@@ -765,3 +783,80 @@ permutation
   s1_clear_snapshot
   s1_func_stats
   s1_commit
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure log_lock_waits behaves correctly
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_unset_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 78a37d9fc8f..c013fbebb88 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,12 @@ 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,
+    waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index cd00f35bf7a..93de87747d4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1910,4 +1910,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 8768e0f27fd..db9948eedb8 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -944,4 +944,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--CwwtKyeZcJiDzEBE
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v8-0003-Introduce-a-new-track_lock_timing-GUC.patch"



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v10 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/aIyNxBWFCybgBZBS%40ip-10-97-1-34.eu-west-3.compute.internal
---
 doc/src/sgml/monitoring.sgml               | 113 ++++++++++++
 src/backend/catalog/system_views.sql       |   9 +
 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/lock-stats.out | 190 +++++++++++++++++++++
 src/test/isolation/isolation_schedule      |   1 +
 src/test/isolation/specs/lock-stats.spec   | 118 +++++++++++++
 src/test/regress/expected/rules.out        |   6 +
 src/test/regress/expected/stats.out        |  48 ++++++
 src/test/regress/sql/stats.sql             |  36 ++++
 12 files changed, 578 insertions(+)
  20.8% doc/src/sgml/
   6.5% src/backend/utils/adt/
  35.5% src/test/isolation/expected/
  19.1% src/test/isolation/specs/
   7.5% src/test/regress/expected/
   5.3% src/test/regress/sql/
   4.9% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 462019a972c..221ee28cc7b 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -509,6 +509,15 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3283,6 +3292,104 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-log-lock-waits"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-log-lock-waits"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5372,6 +5479,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f1ed7b58f13..e54018004db 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,15 @@ 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.waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 9ec61f2b2ec..eccb12b0b68 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 5f907335990..9185a8e6b83 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1741,6 +1741,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	5
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1925,6 +1961,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);
@@ -1942,6 +1979,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 fc8d82665b8..3e2c43e9d1b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6041,6 +6041,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{locktype,waits,wait_time,fastpath_exceeded,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 4a28ea99705..06e2638e788 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -643,6 +643,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_waits(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/lock-stats.out b/src/test/isolation/expected/lock-stats.out
new file mode 100644
index 00000000000..fa9e141dc20
--- /dev/null
+++ b/src/test/isolation/expected/lock-stats.out
@@ -0,0 +1,190 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+f       |f       
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 4e466580cd4..2cc86849c9a 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -43,6 +43,7 @@ test: eval-plan-qual-trigger
 test: inplace-inval
 test: intra-grant-inplace
 test: intra-grant-inplace-db
+test: lock-stats
 test: lock-update-delete
 test: lock-update-traversal
 test: inherit-temp
diff --git a/src/test/isolation/specs/lock-stats.spec b/src/test/isolation/specs/lock-stats.spec
new file mode 100644
index 00000000000..2c5c91791e9
--- /dev/null
+++ b/src/test/isolation/specs/lock-stats.spec
@@ -0,0 +1,118 @@
+setup
+{
+    CREATE TABLE test_stat_tab(key text not null, value int);
+    INSERT INTO test_stat_tab(key, value) VALUES('k0', 1);
+    SELECT pg_stat_force_next_flush();
+}
+
+teardown
+{
+    DROP TABLE IF EXISTS test_stat_tab;
+}
+
+session s1
+setup { SET stats_fetch_consistency = 'none'; }
+step s1_begin { BEGIN; }
+step s1_commit { COMMIT; }
+step s1_table_insert { INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);}
+step s1_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.05); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
+
+session s2
+setup { SET stats_fetch_consistency = 'none'; }
+step s2_begin { BEGIN; }
+step s2_commit { COMMIT; }
+step s2_ff { SELECT pg_stat_force_next_flush(); }
+step s2_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_unset_log_lock_waits { SET log_lock_waits = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure log_lock_waits behaves correctly
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_unset_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 32bea58db2c..2b3cf6d8569 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1952,6 +1952,12 @@ 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,
+    waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b99462bf946..ea7f7846895 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1958,4 +1958,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 941222cf0be..65d8968c83e 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -964,4 +964,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--NJ5Y8xm2S5PE3Q71--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v2 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

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 817fd9f4ca7..3c327093543 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3087,6 +3096,142 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object:
+        <literal>relation</literal>,
+        <literal>extend</literal>,
+        <literal>frozenid</literal>,
+        <literal>page</literal>,
+        <literal>tuple</literal>,
+        <literal>transactionid</literal>,
+        <literal>virtualxid</literal>,
+        <literal>spectoken</literal>,
+        <literal>object</literal>,
+        <literal>userlock</literal>,
+        <literal>advisory</literal>, or
+        <literal>applytransaction</literal>.
+        (See also <xref linkend="wait-event-lock-table"/>.)
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>requests</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of requests for this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>lock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlock_timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>deadlock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlocks</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a deadlock occurred on this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times this lock type was taken via fast path.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5158,6 +5303,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 0a0f95f6bb9..43969783069 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -964,6 +964,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 ef6fffe60b9..602228263a9 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1725,6 +1725,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.
  */
@@ -1909,6 +1945,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);
@@ -1926,6 +1963,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 fd9448ec7b9..bb466f9e5dd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6017,6 +6017,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 913517ba612..38a9a7a9976 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -647,6 +647,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 4286c266e17..304d9f72ed5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1949,6 +1949,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


--u5iTJ95dbl+YppYN--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v4 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml                  | 117 ++++++++++++++++++
 src/backend/catalog/system_views.sql          |  10 ++
 src/backend/utils/activity/pgstat_lock.c      |   8 ++
 src/backend/utils/adt/pgstatfuncs.c           |  40 ++++++
 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           |   7 ++
 src/test/regress/sql/advisory_lock.sql        |   4 +
 11 files changed, 237 insertions(+), 2 deletions(-)
  46.2% doc/src/sgml/
   3.2% src/backend/catalog/
  16.3% src/backend/utils/adt/
   6.2% src/include/catalog/
   9.3% src/test/isolation/expected/
   6.7% src/test/isolation/specs/
   7.3% src/test/regress/expected/
   4.4% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b77d189a500..1cba84c1542 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,108 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_stat_lock</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>requests</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of requests for this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>lock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlock_timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>deadlock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times this lock type was taken via fast path.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5306,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..a51ae67de05 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,16 @@ 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.timeouts,
+            l.deadlock_timeouts,
+            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 110a370b875..e26e24c260c 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 b1df96e7b0b..6a699da8bf0 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1737,6 +1737,43 @@ 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	6
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->requests);
+		values[i++] = Int64GetDatum(lck_stats->timeouts);
+		values[i++] = Int64GetDatum(lck_stats->deadlock_timeouts);
+		values[i++] = Int64GetDatum(lck_stats->fastpath);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1921,6 +1958,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);
@@ -1938,6 +1976,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 83f6501df38..e208ebdbbc3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6026,6 +6026,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o}',
+  proargnames => '{locktype,requests,timeouts,deadlock_timeouts,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 8460e2db159..b06a261572b 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -643,6 +643,7 @@ extern void pgstat_count_lock_requests(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_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..5ae5a8bf953 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 deadlock_timeouts > 0 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?
+--------
+t       
+(1 row)
+
diff --git a/src/test/isolation/specs/deadlock-hard.spec b/src/test/isolation/specs/deadlock-hard.spec
index 60bedca237a..d9ed99836e3 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 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 78a37d9fc8f..80999465baf 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,13 @@ 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,
+    timeouts,
+    deadlock_timeouts,
+    fastpath,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, requests, timeouts, deadlock_timeouts, 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


--3ZD47sYrxKE4JxfA--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v5 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml             | 113 +++++++++++++++++
 src/backend/catalog/system_views.sql     |   9 ++
 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/stats.out    | 147 +++++++++++++++++++++++
 src/test/isolation/expected/stats_1.out  | 147 +++++++++++++++++++++++
 src/test/isolation/specs/stats.spec      |  76 ++++++++++++
 src/test/regress/expected/rules.out      |   6 +
 src/test/regress/expected/stats.out      |  42 +++++++
 src/test/regress/sql/stats.sql           |  30 +++++
 12 files changed, 627 insertions(+)
  18.8% doc/src/sgml/
   5.9% src/backend/utils/adt/
  49.4% src/test/isolation/expected/
  11.3% src/test/isolation/specs/
   6.0% src/test/regress/expected/
   4.1% src/test/regress/sql/
   4.3% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b77d189a500..3a196bc305c 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,104 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-log-lock-waits"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-log-lock-waits"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5302,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..22efeb713e4 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,15 @@ 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.waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 7af4bc6d018..cd4399b5ef6 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 b1df96e7b0b..27ca2d8f07c 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1737,6 +1737,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	5
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1921,6 +1957,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);
@@ -1938,6 +1975,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 83f6501df38..c224e842e32 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6026,6 +6026,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{locktype,waits,wait_time,fastpath_exceeded,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 dfbead474b1..175e431ed81 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -641,6 +641,7 @@ extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_waits(uint8 locktag_type);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_wait_time(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index cfad309ccf3..d3cb8715d95 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -3751,3 +3751,150 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index e1d937784cb..89d2e4cc6b4 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -3775,3 +3775,150 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index da16710da0f..c600fc11157 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -130,6 +130,14 @@ step s1_slru_check_stats {
   WHERE before.stat = 'blks_zeroed';
 	}
 
+# Lock stats steps
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.5); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 session s2
 setup { SET stats_fetch_consistency = 'none'; }
@@ -164,6 +172,15 @@ step s2_big_notify { SELECT pg_notify('stats_test_use',
                 repeat(i::text, current_setting('block_size')::int / 2)) FROM generate_series(1, 3) g(i);
                 }
 
+# Lock stats steps
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_report_stat_lock_relation { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 ######################
 # Function stats tests
@@ -765,3 +782,62 @@ permutation
   s1_clear_snapshot
   s1_func_stats
   s1_commit
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 78a37d9fc8f..c013fbebb88 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,12 @@ 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,
+    waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index cd00f35bf7a..ddb016e73d2 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1910,4 +1910,46 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create 70 partitions (exceeds default number of slots)
+DO $$
+BEGIN
+  FOR i IN 1..70 LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 8768e0f27fd..fac7d734758 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -944,4 +944,34 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create 70 partitions (exceeds default number of slots)
+DO $$
+BEGIN
+  FOR i IN 1..70 LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--n9KlQA279nLbrjqm--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v9 2/3] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/monitoring.sgml             | 113 ++++++++++++++
 src/backend/catalog/system_views.sql     |   9 ++
 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/stats.out    | 189 +++++++++++++++++++++++
 src/test/isolation/expected/stats_1.out  | 189 +++++++++++++++++++++++
 src/test/isolation/specs/stats.spec      |  95 ++++++++++++
 src/test/regress/expected/rules.out      |   6 +
 src/test/regress/expected/stats.out      |  48 ++++++
 src/test/regress/sql/stats.sql           |  36 +++++
 12 files changed, 742 insertions(+)
  15.9% doc/src/sgml/
   5.0% src/backend/utils/adt/
  54.1% src/test/isolation/expected/
  11.3% src/test/isolation/specs/
   5.7% src/test/regress/expected/
   4.0% src/test/regress/sql/
   3.6% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 462019a972c..221ee28cc7b 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -509,6 +509,15 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3283,6 +3292,104 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-log-lock-waits"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-log-lock-waits"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5372,6 +5479,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f1ed7b58f13..e54018004db 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,15 @@ 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.waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 91a53a6013a..954fa51c3e0 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 5f907335990..9185a8e6b83 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1741,6 +1741,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	5
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1925,6 +1961,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);
@@ -1942,6 +1979,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 fc8d82665b8..3e2c43e9d1b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6041,6 +6041,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{locktype,waits,wait_time,fastpath_exceeded,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 4a28ea99705..06e2638e788 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -643,6 +643,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_waits(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index cfad309ccf3..3cae3052e40 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -3751,3 +3751,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+f       |f       
+(1 row)
+
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index e1d937784cb..ea4fd97a9a5 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -3775,3 +3775,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+f       |f       
+(1 row)
+
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index da16710da0f..42be68c545f 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -130,6 +130,14 @@ step s1_slru_check_stats {
   WHERE before.stat = 'blks_zeroed';
 	}
 
+# Lock stats steps
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.5); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 session s2
 setup { SET stats_fetch_consistency = 'none'; }
@@ -164,6 +172,16 @@ step s2_big_notify { SELECT pg_notify('stats_test_use',
                 repeat(i::text, current_setting('block_size')::int / 2)) FROM generate_series(1, 3) g(i);
                 }
 
+# Lock stats steps
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_unset_log_lock_waits { SET log_lock_waits = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 ######################
 # Function stats tests
@@ -765,3 +783,80 @@ permutation
   s1_clear_snapshot
   s1_func_stats
   s1_commit
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure log_lock_waits behaves correctly
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_unset_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 32bea58db2c..2b3cf6d8569 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1952,6 +1952,12 @@ 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,
+    waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b99462bf946..ea7f7846895 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1958,4 +1958,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 941222cf0be..65d8968c83e 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -964,4 +964,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--nK2xgkLTeiiswcxd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v9-0003-Introduce-a-new-track_lock_timing-GUC.patch"



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v11 3/3] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/aIyNxBWFCybgBZBS%40ip-10-97-1-34.eu-west-3.compute.internal
---
 doc/src/sgml/monitoring.sgml               | 111 ++++++++++++
 src/backend/catalog/system_views.sql       |   9 +
 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/lock-stats.out | 190 +++++++++++++++++++++
 src/test/isolation/isolation_schedule      |   1 +
 src/test/isolation/specs/lock-stats.spec   | 125 ++++++++++++++
 src/test/regress/expected/rules.out        |   6 +
 src/test/regress/expected/stats.out        |  48 ++++++
 src/test/regress/sql/stats.sql             |  36 ++++
 12 files changed, 583 insertions(+)
  19.9% doc/src/sgml/
   6.4% src/backend/utils/adt/
  35.1% src/test/isolation/expected/
  20.8% src/test/isolation/specs/
   7.4% src/test/regress/expected/
   5.2% src/test/regress/sql/
   4.8% src/

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 462019a972c..d590ecad541 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -509,6 +509,15 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3283,6 +3292,102 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when the lock was successfully
+        acquired after waiting longer than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when the lock was successfully acquired after waiting
+        longer than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5372,6 +5477,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f1ed7b58f13..e54018004db 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,15 @@ 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.waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 fe330eb99b1..76749e4724d 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 5f907335990..9185a8e6b83 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1741,6 +1741,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	5
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1925,6 +1961,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);
@@ -1942,6 +1979,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 fc8d82665b8..3e2c43e9d1b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6041,6 +6041,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{locktype,waits,wait_time,fastpath_exceeded,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 553106025da..dadf8b1c1ed 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -643,6 +643,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_waits(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/lock-stats.out b/src/test/isolation/expected/lock-stats.out
new file mode 100644
index 00000000000..556c210a28b
--- /dev/null
+++ b/src/test/isolation/expected/lock-stats.out
@@ -0,0 +1,190 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_set_log_lock_waits s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_log_lock_waits: SET log_lock_waits = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_log_lock_waits s2_set_deadlock_timeout s2_unset_log_lock_waits s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_log_lock_waits: SET log_lock_waits = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_log_lock_waits: SET log_lock_waits = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.05);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?
+--------+--------
+t       |t       
+(1 row)
+
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 4e466580cd4..2cc86849c9a 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -43,6 +43,7 @@ test: eval-plan-qual-trigger
 test: inplace-inval
 test: intra-grant-inplace
 test: intra-grant-inplace-db
+test: lock-stats
 test: lock-update-delete
 test: lock-update-traversal
 test: inherit-temp
diff --git a/src/test/isolation/specs/lock-stats.spec b/src/test/isolation/specs/lock-stats.spec
new file mode 100644
index 00000000000..e4e66a9fb4d
--- /dev/null
+++ b/src/test/isolation/specs/lock-stats.spec
@@ -0,0 +1,125 @@
+# Test for the lock statistics
+#
+# This test creates multiple locking situations when a session (s2) has to
+# wait on a lock for longer than the deadlock timeout. The first permutations
+# each test a dedicated lock type. The last permutation ensures that
+# log_lock_waits has no impact on the statistics counters.
+
+setup
+{
+    CREATE TABLE test_stat_tab(key text not null, value int);
+    INSERT INTO test_stat_tab(key, value) VALUES('k0', 1);
+    SELECT pg_stat_force_next_flush();
+}
+
+teardown
+{
+    DROP TABLE IF EXISTS test_stat_tab;
+}
+
+session s1
+setup { SET stats_fetch_consistency = 'none'; }
+step s1_begin { BEGIN; }
+step s1_commit { COMMIT; }
+step s1_table_insert { INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);}
+step s1_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_log_lock_waits { SET log_lock_waits = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.05); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
+
+session s2
+setup { SET stats_fetch_consistency = 'none'; }
+step s2_begin { BEGIN; }
+step s2_commit { COMMIT; }
+step s2_ff { SELECT pg_stat_force_next_flush(); }
+step s2_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_log_lock_waits { SET log_lock_waits = on; }
+step s2_unset_log_lock_waits { SET log_lock_waits = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, wait_time > 50 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_set_log_lock_waits
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure log_lock_waits has no impact
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_log_lock_waits
+  s2_set_deadlock_timeout
+  s2_unset_log_lock_waits
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 32bea58db2c..2b3cf6d8569 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1952,6 +1952,12 @@ 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,
+    waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b99462bf946..ea7f7846895 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1958,4 +1958,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 941222cf0be..65d8968c83e 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -964,4 +964,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--YVZ2rdHTqHRFZ4HX--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v1 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>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
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object:
+        <literal>relation</literal>,
+        <literal>extend</literal>,
+        <literal>frozenid</literal>,
+        <literal>page</literal>,
+        <literal>tuple</literal>,
+        <literal>transactionid</literal>,
+        <literal>virtualxid</literal>,
+        <literal>spectoken</literal>,
+        <literal>object</literal>,
+        <literal>userlock</literal>,
+        <literal>advisory</literal>, or
+        <literal>applytransaction</literal>.
+        (See also <xref linkend="wait-event-lock-table"/>.)
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>requests</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of requests for this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>lock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlock_timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>deadlock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlocks</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a deadlock occurred on this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times this lock type was taken via fast path.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5044,6 +5189,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: 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


--Th7cMauVQntl/Hn1--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v3 2/2] Add the pg_stat_lock view
@ 2025-07-31 09:35 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2025-07-31 09:35 UTC (permalink / raw)

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 b77d189a500..927b62cf649 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,142 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object:
+        <literal>relation</literal>,
+        <literal>extend</literal>,
+        <literal>frozenid</literal>,
+        <literal>page</literal>,
+        <literal>tuple</literal>,
+        <literal>transactionid</literal>,
+        <literal>virtualxid</literal>,
+        <literal>spectoken</literal>,
+        <literal>object</literal>,
+        <literal>userlock</literal>,
+        <literal>advisory</literal>, or
+        <literal>applytransaction</literal>.
+        (See also <xref linkend="wait-event-lock-table"/>.)
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>requests</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of requests for this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>lock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlock_timeouts</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times requests for this lock type had to wait longer
+        than <varname>deadlock_timeout</varname>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>deadlocks</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a deadlock occurred on this lock type.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times this lock type was taken via fast path.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5340,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..e30c25ced31 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,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 73ca0bb0b7f..4ca4a3f3b9b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1725,6 +1725,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.
  */
@@ -1909,6 +1945,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);
@@ -1926,6 +1963,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 83f6501df38..a0c47f6888c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6026,6 +6026,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 cbe226b9a07..c16c3335755 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -647,6 +647,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 f9bc213e5a1..8ee2e4cbea1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,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


--PI+wla+m5/GDNJVD--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH v6 2/2] Add the pg_stat_lock view
@ 2026-02-19 10:19 Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Bertrand Drouvot @ 2026-02-19 10:19 UTC (permalink / raw)

This new view reports lock statistics.

Note that it does not omit combinations which do not make sense (as pg_locks
does).

Also wait_time is reported as bigint as the deadlock_timeout default value is
1s.

This commit also adds documentation and a few tests.

XXX: Bump catversion
---
 doc/src/sgml/config.sgml                 |   4 +-
 doc/src/sgml/monitoring.sgml             | 125 +++++++++++++++
 src/backend/catalog/system_views.sql     |  10 ++
 src/backend/utils/activity/pgstat_lock.c |   8 +
 src/backend/utils/adt/pgstatfuncs.c      |  40 +++++
 src/include/catalog/pg_proc.dat          |   9 ++
 src/include/pgstat.h                     |   1 +
 src/test/isolation/expected/stats.out    | 189 +++++++++++++++++++++++
 src/test/isolation/expected/stats_1.out  | 189 +++++++++++++++++++++++
 src/test/isolation/specs/stats.spec      |  95 ++++++++++++
 src/test/regress/expected/rules.out      |   7 +
 src/test/regress/expected/stats.out      |  48 ++++++
 src/test/regress/sql/stats.sql           |  36 +++++
 13 files changed, 760 insertions(+), 1 deletion(-)
  17.5% doc/src/sgml/
   4.9% src/backend/utils/adt/
  53.2% src/test/isolation/expected/
  11.1% src/test/isolation/specs/
   5.5% src/test/regress/expected/
   3.8% src/test/regress/sql/
   3.6% src/

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0e868fb03e4..b3c353d6095 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8854,7 +8854,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
        <para>
         Enables timing of lock waits. This parameter is on by default, as it tracks
         only the timings for successful acquisitions that waited longer than
-        <xref linkend="guc-deadlock-timeout"/>.
+        <xref linkend="guc-deadlock-timeout"/>. Lock timing information is
+        displayed in the <link linkend="monitoring-pg-stat-lock-view">
+        <structname>pg_stat_lock</structname></link> view.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
        </para>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b77d189a500..277275fdcb5 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
      </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry>
+      <entry>
+       One row for each lock type, containing cluster-wide locks statistics.
+       See <link linkend="monitoring-pg-stat-lock-view">
+       <structname>pg_stat_lock</structname></link> for details.
+     </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_replication_slots</structname><indexterm><primary>pg_stat_replication_slots</primary></indexterm></entry>
       <entry>One row per replication slot, showing statistics about the
@@ -3124,6 +3133,116 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
  </sect2>
 
+
+ <sect2 id="monitoring-pg-stat-lock-view">
+  <title><structname>pg_stat_lock</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_lock</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_lock</structname> view will contain one row for each
+   lock type, showing cluster-wide locks statistics.
+  </para>
+
+  <table id="pg-stat-lock-view" xreflabel="pg_stat_lock">
+   <title><structname>pg_stat_lock</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        Column Type
+       </para>
+       <para>
+        Description
+       </para>
+      </entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>locktype</structfield> <type>text</type>
+       </para>
+       <para>
+        Type of the lockable object. See <link linkend="view-pg-locks">
+        <structname>pg_locks</structname></link> for details.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>timed_waits</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type had to wait because of a
+        conflicting lock. Only incremented when <xref linkend="guc-track-lock-timing"/>
+        is enabled and the lock was successfully acquired after waiting longer
+        than <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>wait_time</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Total time spent waiting for locks of this type, in milliseconds.
+        Only incremented when <xref linkend="guc-track-lock-timing"/> is enabled and
+        the lock was successfully acquired after waiting longer than
+        <xref linkend="guc-deadlock-timeout"/>.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>fastpath_exceeded</structfield> <type>bigint</type>
+       </para>
+       <para>
+        Number of times a lock of this type could not be acquired via fast path
+        because the fast path slot limit was exceeded. You may want to increase
+        <xref linkend="guc-max-locks-per-transaction"/> if you feel this counter
+        is too high.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry">
+       <para role="column_definition">
+        <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which these statistics were last reset.
+       </para>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-bgwriter-view">
   <title><structname>pg_stat_bgwriter</structname></title>
 
@@ -5195,6 +5314,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_io</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>lock</literal>: Reset all the counters shown in the
+          <structname>pg_stat_lock</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>recovery_prefetch</literal>: Reset all the counters shown in
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1ea8f1faa9e..dcf12a202e0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -985,6 +985,16 @@ 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.waits,
+            l.timed_waits,
+            l.wait_time,
+            l.fastpath_exceeded,
+            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 b410f376d49..451fba4f8d9 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 b1df96e7b0b..3b7cbdb82be 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1737,6 +1737,43 @@ 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	6
+	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];
+		int			i = 0;
+
+		locktypename = LockTagTypeNames[lcktype];
+
+		values[i++] = CStringGetTextDatum(locktypename);
+		values[i++] = Int64GetDatum(lck_stats->waits);
+		values[i++] = Int64GetDatum(lck_stats->timed_waits);
+		values[i++] = Int64GetDatum(lck_stats->wait_time);
+		values[i++] = Int64GetDatum(lck_stats->fastpath_exceeded);
+		values[i] = TimestampTzGetDatum(lock_stats->stat_reset_timestamp);
+
+		Assert(i + 1 == PG_STAT_LOCK_COLS);
+
+		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of SLRU caches.
  */
@@ -1921,6 +1958,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);
@@ -1938,6 +1976,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 dac40992cbc..3cd497a8e1f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6027,6 +6027,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,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o}',
+  proargnames => '{locktype,waits,timed_waits,wait_time,fastpath_exceeded,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 615ccf540e6..39441d9698a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -642,6 +642,7 @@ extern void pgstat_lock_flush(bool nowait);
 extern void pgstat_count_lock_waits(uint8 locktag_type);
 extern void pgstat_count_lock_fastpath_exceeded(uint8 locktag_type);
 extern void pgstat_count_lock_timed_wait(uint8 locktag_type, long msecs);
+extern PgStat_Lock *pgstat_fetch_stat_lock(void);
 
 /*
  * Functions in pgstat_database.c
diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
index cfad309ccf3..f9cd78f03b3 100644
--- a/src/test/isolation/expected/stats.out
+++ b/src/test/isolation/expected/stats.out
@@ -3751,3 +3751,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_unset_track_lock_timing s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_track_lock_timing: SET track_lock_timing = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |f       |f       
+(1 row)
+
diff --git a/src/test/isolation/expected/stats_1.out b/src/test/isolation/expected/stats_1.out
index e1d937784cb..955f6ff5ec0 100644
--- a/src/test/isolation/expected/stats_1.out
+++ b/src/test/isolation/expected/stats_1.out
@@ -3775,3 +3775,192 @@ test_stat_func|                         1|t               |t
 (1 row)
 
 step s1_commit: COMMIT;
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_table_insert s1_begin s1_table_update_k1 s2_begin s2_ff s2_table_update_k1 s1_sleep s1_commit s2_commit s2_report_stat_lock_transactionid
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1'; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_table_update_k1: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_transactionid: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_set_track_lock_timing s1_lock_advisory_lock s2_begin s2_ff s2_lock_advisory_lock s1_sleep s1_lock_advisory_unlock s2_lock_advisory_unlock s2_commit s2_report_stat_lock_advisory
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_set_track_lock_timing: SET track_lock_timing = on;
+step s1_lock_advisory_lock: SELECT pg_advisory_lock(1);
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_advisory_lock: SELECT pg_advisory_lock(1); <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_lock_advisory_lock: <... completed>
+pg_advisory_lock
+----------------
+                
+(1 row)
+
+step s2_lock_advisory_unlock: SELECT pg_advisory_unlock(1);
+pg_advisory_unlock
+------------------
+t                 
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_report_stat_lock_advisory: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory';
+?column?|?column?|?column?
+--------+--------+--------
+t       |t       |t       
+(1 row)
+
+
+starting permutation: s1_set_deadlock_timeout s1_reset_stat_lock s1_set_track_lock_timing s2_set_deadlock_timeout s2_unset_track_lock_timing s1_begin s1_lock_relation s2_begin s2_ff s2_lock_relation s1_sleep s1_commit s2_commit s2_report_stat_lock_relation
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s1_reset_stat_lock: SELECT pg_stat_reset_shared('lock');
+pg_stat_reset_shared
+--------------------
+                    
+(1 row)
+
+step s1_set_track_lock_timing: SET track_lock_timing = on;
+step s2_set_deadlock_timeout: SET deadlock_timeout = '10ms';
+step s2_unset_track_lock_timing: SET track_lock_timing = off;
+step s1_begin: BEGIN;
+step s1_lock_relation: LOCK TABLE test_stat_tab;
+step s2_begin: BEGIN;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_lock_relation: LOCK TABLE test_stat_tab; <waiting ...>
+step s1_sleep: SELECT pg_sleep(0.5);
+pg_sleep
+--------
+        
+(1 row)
+
+step s1_commit: COMMIT;
+step s2_lock_relation: <... completed>
+step s2_commit: COMMIT;
+step s2_report_stat_lock_relation: SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation';
+?column?|?column?|?column?
+--------+--------+--------
+t       |f       |f       
+(1 row)
+
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
index da16710da0f..7c8e2a9214e 100644
--- a/src/test/isolation/specs/stats.spec
+++ b/src/test/isolation/specs/stats.spec
@@ -130,6 +130,14 @@ step s1_slru_check_stats {
   WHERE before.stat = 'blks_zeroed';
 	}
 
+# Lock stats steps
+step s1_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s1_set_track_lock_timing { SET track_lock_timing = on; }
+step s1_reset_stat_lock { SELECT pg_stat_reset_shared('lock'); }
+step s1_sleep { SELECT pg_sleep(0.5); }
+step s1_lock_relation { LOCK TABLE test_stat_tab; }
+step s1_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s1_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 session s2
 setup { SET stats_fetch_consistency = 'none'; }
@@ -164,6 +172,16 @@ step s2_big_notify { SELECT pg_notify('stats_test_use',
                 repeat(i::text, current_setting('block_size')::int / 2)) FROM generate_series(1, 3) g(i);
                 }
 
+# Lock stats steps
+step s2_set_deadlock_timeout { SET deadlock_timeout = '10ms'; }
+step s2_set_track_lock_timing { SET track_lock_timing = on; }
+step s2_unset_track_lock_timing { SET track_lock_timing = off; }
+step s2_report_stat_lock_relation { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'relation'; }
+step s2_report_stat_lock_transactionid { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'transactionid'; }
+step s2_report_stat_lock_advisory { SELECT waits > 0, timed_waits = waits, wait_time > 500 FROM pg_stat_lock WHERE locktype = 'advisory'; }
+step s2_lock_relation { LOCK TABLE test_stat_tab; }
+step s2_lock_advisory_lock { SELECT pg_advisory_lock(1); }
+step s2_lock_advisory_unlock { SELECT pg_advisory_unlock(1); }
 
 ######################
 # Function stats tests
@@ -765,3 +783,80 @@ permutation
   s1_clear_snapshot
   s1_func_stats
   s1_commit
+
+######################
+# Lock stats tests
+######################
+
+# relation lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_track_lock_timing
+  s2_set_deadlock_timeout
+  s2_set_track_lock_timing
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
+
+# transaction lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_track_lock_timing
+  s2_set_deadlock_timeout
+  s2_set_track_lock_timing
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1
+  s2_begin
+  s2_ff
+  s2_table_update_k1
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_transactionid
+
+# advisory lock
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_track_lock_timing
+  s2_set_deadlock_timeout
+  s2_set_track_lock_timing
+  s1_lock_advisory_lock
+  s2_begin
+  s2_ff
+  s2_lock_advisory_lock
+  s1_sleep
+  s1_lock_advisory_unlock
+  s2_lock_advisory_unlock
+  s2_commit
+  s2_report_stat_lock_advisory
+
+# Ensure track_lock_timing behaves correctly
+
+permutation
+  s1_set_deadlock_timeout
+  s1_reset_stat_lock
+  s1_set_track_lock_timing
+  s2_set_deadlock_timeout
+  s2_unset_track_lock_timing
+  s1_begin
+  s1_lock_relation
+  s2_begin
+  s2_ff
+  s2_lock_relation
+  s1_sleep
+  s1_commit
+  s2_commit
+  s2_report_stat_lock_relation
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 78a37d9fc8f..2fcb48df6c9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1951,6 +1951,13 @@ 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,
+    waits,
+    timed_waits,
+    wait_time,
+    fastpath_exceeded,
+    stats_reset
+   FROM pg_stat_get_lock() l(locktype, waits, timed_waits, wait_time, fastpath_exceeded, stats_reset);
 pg_stat_progress_analyze| SELECT s.pid,
     s.datid,
     d.datname,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index cd00f35bf7a..93de87747d4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1910,4 +1910,52 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 (1 row)
 
 DROP TABLE table_fillfactor;
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+SELECT pg_stat_reset_shared('lock');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+ count 
+-------
+     0
+(1 row)
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE part_test;
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 8768e0f27fd..db9948eedb8 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -944,4 +944,40 @@ SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor');
 
 DROP TABLE table_fillfactor;
 
+-- Test fastpath_exceeded stat
+CREATE TABLE part_test (id int) PARTITION BY RANGE (id);
+
+SELECT pg_stat_reset_shared('lock');
+
+-- Create partitions (exceeds number of slots)
+DO $$
+DECLARE
+  max_locks int;
+BEGIN
+  SELECT setting::int INTO max_locks
+  FROM pg_settings
+  WHERE name = 'max_locks_per_transaction';
+
+  FOR i IN 1..(max_locks + 10) LOOP
+    EXECUTE format(
+      'CREATE TABLE part_test_%s PARTITION OF part_test
+       FOR VALUES FROM (%s) TO (%s)',
+      i, (i-1)*1000, i*1000
+    );
+  END LOOP;
+END;
+$$;
+
+SELECT fastpath_exceeded AS fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation' \gset
+
+-- Needs a lock on each partition
+SELECT count(*) FROM part_test;
+
+-- Ensure pending stats are flushed
+SELECT pg_stat_force_next_flush();
+
+SELECT fastpath_exceeded > :fastpath_exceeded_before FROM pg_stat_lock WHERE locktype = 'relation';
+
+DROP TABLE part_test;
+
 -- End of Stats Test
-- 
2.34.1


--7AmA4UfQBKkkaxHa--





^ permalink  raw  reply  [nested|flat] 14+ messages in thread


end of thread, other threads:[~2026-02-19 10:19 UTC | newest]

Thread overview: 14+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-10-02 19:55 [PATCH v7 15/16] Trigger authors need not worry about parallelism Karl O. Pinc <[email protected]>
2023-10-02 19:55 [PATCH v6 15/15] Trigger authors need not worry about parallelism Karl O. Pinc <[email protected]>
2024-03-18 16:02 [PATCH v6 2/2] Add support for AVX2 in simd.h. Nathan Bossart <[email protected]>
2025-07-31 09:35 [PATCH v7 2/3] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v8 2/3] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v10 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v2 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v4 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v5 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v9 2/3] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v11 3/3] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v1 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2025-07-31 09:35 [PATCH v3 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>
2026-02-19 10:19 [PATCH v6 2/2] Add the pg_stat_lock view Bertrand Drouvot <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox