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 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 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