pg-lock-tracer 0.5.4__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pg_lock_tracer/__init__.py +1 -1
- pg_lock_tracer/bpf/pg_row_lock_tracer.c +121 -0
- pg_lock_tracer/helper.py +56 -0
- pg_lock_tracer/pg_lock_tracer.py +149 -69
- pg_lock_tracer/pg_lw_lock_tracer.py +12 -19
- pg_lock_tracer/pg_row_lock_tracer.py +372 -0
- {pg_lock_tracer-0.5.4.dist-info → pg_lock_tracer-0.6.0.dist-info}/METADATA +73 -7
- pg_lock_tracer-0.6.0.dist-info/RECORD +17 -0
- {pg_lock_tracer-0.5.4.dist-info → pg_lock_tracer-0.6.0.dist-info}/WHEEL +1 -1
- {pg_lock_tracer-0.5.4.dist-info → pg_lock_tracer-0.6.0.dist-info}/entry_points.txt +1 -0
- pg_lock_tracer-0.5.4.dist-info/RECORD +0 -15
- {pg_lock_tracer-0.5.4.dist-info → pg_lock_tracer-0.6.0.dist-info}/LICENSE +0 -0
- {pg_lock_tracer-0.5.4.dist-info → pg_lock_tracer-0.6.0.dist-info}/top_level.txt +0 -0
pg_lock_tracer/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#include <uapi/linux/ptrace.h>
|
|
2
|
+
|
|
3
|
+
/* Placeholder for auto generated defines */
|
|
4
|
+
__DEFINES__
|
|
5
|
+
|
|
6
|
+
typedef struct RowLockEvent_t {
|
|
7
|
+
u32 pid;
|
|
8
|
+
u64 timestamp;
|
|
9
|
+
u32 event_type;
|
|
10
|
+
|
|
11
|
+
/* See RelFileNode - Oid is u32 */
|
|
12
|
+
u32 tablespace;
|
|
13
|
+
u32 database;
|
|
14
|
+
u32 relation;
|
|
15
|
+
|
|
16
|
+
/* LockTupleMode */
|
|
17
|
+
u8 locktuplemode;
|
|
18
|
+
|
|
19
|
+
/* LockWaitPolicy */
|
|
20
|
+
u8 lockwaitpolicy;
|
|
21
|
+
|
|
22
|
+
/* Locked tuple */
|
|
23
|
+
u32 blockid;
|
|
24
|
+
u16 offset;
|
|
25
|
+
|
|
26
|
+
/* TM_Result */
|
|
27
|
+
int lockresult;
|
|
28
|
+
} RowLockEvent;
|
|
29
|
+
|
|
30
|
+
BPF_PERF_OUTPUT(lockevents);
|
|
31
|
+
|
|
32
|
+
static void fill_and_submit(struct pt_regs *ctx, RowLockEvent *event) {
|
|
33
|
+
event->pid = bpf_get_current_pid_tgid();
|
|
34
|
+
event->timestamp = bpf_ktime_get_ns();
|
|
35
|
+
|
|
36
|
+
// sudo cat /sys/kernel/debug/tracing/trace_pipe
|
|
37
|
+
// bpf_trace_printk("LW lock event for trance: %s\\n", tranche);
|
|
38
|
+
|
|
39
|
+
lockevents.perf_submit(ctx, event, sizeof(RowLockEvent));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Acquire a tuple lock
|
|
44
|
+
*
|
|
45
|
+
* Arguments:
|
|
46
|
+
* 1. Relation relation (1st member RelFileNode)
|
|
47
|
+
* 2. ItemPointer tid
|
|
48
|
+
* 3. Snapshot snapshot,
|
|
49
|
+
* 4. TupleTableSlot *slot,
|
|
50
|
+
* 5. CommandId cid,
|
|
51
|
+
* 6. LockTupleMode mode,
|
|
52
|
+
* 7. LockWaitPolicy wait_policy,
|
|
53
|
+
* 8. uint8 flags,
|
|
54
|
+
* 9. TM_FailureData *tmfd
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
int heapam_tuple_lock(struct pt_regs *ctx) {
|
|
58
|
+
RowLockEvent event = {.event_type = EVENT_LOCK_TUPLE};
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* (gdb) ptype /o RelFileNode
|
|
62
|
+
* 0 | 4 Oid spcNode;
|
|
63
|
+
* 4 | 4 Oid dbNode;
|
|
64
|
+
* 8 | 4 Oid relNode;
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
char buffer_relation[12];
|
|
68
|
+
bpf_probe_read_user(buffer_relation, sizeof(buffer_relation),
|
|
69
|
+
(void *)PT_REGS_PARM1(ctx));
|
|
70
|
+
bpf_probe_read_kernel(&(event.tablespace), sizeof(event.tablespace),
|
|
71
|
+
&(buffer_relation[0]));
|
|
72
|
+
bpf_probe_read_kernel(&(event.database), sizeof(event.database),
|
|
73
|
+
&(buffer_relation[4]));
|
|
74
|
+
bpf_probe_read_kernel(&(event.relation), sizeof(event.relation),
|
|
75
|
+
&(buffer_relation[8]));
|
|
76
|
+
|
|
77
|
+
/* Locked tuple */
|
|
78
|
+
char buffer_item_pointer[6];
|
|
79
|
+
u16 bi_hi;
|
|
80
|
+
u16 bi_lo;
|
|
81
|
+
|
|
82
|
+
bpf_probe_read_user(buffer_item_pointer, sizeof(buffer_item_pointer),
|
|
83
|
+
(void *)PT_REGS_PARM2(ctx));
|
|
84
|
+
bpf_probe_read_kernel(&(bi_hi), sizeof(bi_hi), &(buffer_item_pointer[0]));
|
|
85
|
+
bpf_probe_read_kernel(&(bi_lo), sizeof(bi_lo), &(buffer_item_pointer[2]));
|
|
86
|
+
bpf_probe_read_kernel(&(event.offset), sizeof(event.offset),
|
|
87
|
+
&(buffer_item_pointer[4]));
|
|
88
|
+
|
|
89
|
+
/* See #define BlockIdGetBlockNumber(blockId) */
|
|
90
|
+
event.blockid = (bi_hi) << 16 | bi_lo;
|
|
91
|
+
|
|
92
|
+
/* Locking options */
|
|
93
|
+
bpf_probe_read_kernel(&(event.locktuplemode), sizeof(event.locktuplemode),
|
|
94
|
+
&(PT_REGS_PARM6(ctx)));
|
|
95
|
+
|
|
96
|
+
/* Only the first six function parameters are passed via register. All
|
|
97
|
+
* remaining parameters are stored on the stack.
|
|
98
|
+
*
|
|
99
|
+
* See: System V Application Binary Interface—AMD64 Architecture Processor
|
|
100
|
+
* Supplement.
|
|
101
|
+
*/
|
|
102
|
+
void *ptr = 0;
|
|
103
|
+
bpf_probe_read(&ptr, sizeof(ptr), (void *)(PT_REGS_SP(ctx) + (1 * 8)));
|
|
104
|
+
bpf_probe_read_kernel(&(event.lockwaitpolicy), sizeof(event.lockwaitpolicy),
|
|
105
|
+
&ptr);
|
|
106
|
+
|
|
107
|
+
fill_and_submit(ctx, &event);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* Acquire a tuple lock - Function done
|
|
113
|
+
*/
|
|
114
|
+
int heapam_tuple_lock_end(struct pt_regs *ctx) {
|
|
115
|
+
RowLockEvent event = {.event_type = EVENT_LOCK_TUPLE_END};
|
|
116
|
+
|
|
117
|
+
event.lockresult = PT_REGS_RC(ctx);
|
|
118
|
+
|
|
119
|
+
fill_and_submit(ctx, &event);
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
pg_lock_tracer/helper.py
CHANGED
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from bcc import BPF
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class PostgreSQLLockHelper:
|
|
11
13
|
|
|
@@ -91,6 +93,9 @@ class PostgreSQLLockHelper:
|
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
class BPFHelper:
|
|
96
|
+
# The size of the kernel ring buffer
|
|
97
|
+
page_cnt = 2048
|
|
98
|
+
|
|
94
99
|
@staticmethod
|
|
95
100
|
def enum_to_defines(enum_instance, prefix):
|
|
96
101
|
"""
|
|
@@ -115,3 +120,54 @@ class BPFHelper:
|
|
|
115
120
|
|
|
116
121
|
with program_file.open("r") as bpf_program:
|
|
117
122
|
return bpf_program.read()
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def check_pid_exe(pids, executable):
|
|
126
|
+
"""
|
|
127
|
+
Do the given PIDs belong to the executable
|
|
128
|
+
"""
|
|
129
|
+
if not pids:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
for pid in pids:
|
|
133
|
+
if not os.path.isdir(f"/proc/{pid}"):
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"/proc entry for pid {pid} not found, does the process exist?"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
binary = os.readlink(f"/proc/{pid}/exe")
|
|
139
|
+
|
|
140
|
+
if binary != executable:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
f"Pid {pid} does not belong to binary {executable}. Executable is {binary}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def register_ebpf_probe(
|
|
147
|
+
path, bpf_instance, function_regex, bpf_fn_name, verbose, probe_on_enter=True
|
|
148
|
+
):
|
|
149
|
+
"""
|
|
150
|
+
Register a BPF probe
|
|
151
|
+
"""
|
|
152
|
+
addresses = set()
|
|
153
|
+
func_and_addr = BPF.get_user_functions_and_addresses(path, function_regex)
|
|
154
|
+
|
|
155
|
+
if not func_and_addr:
|
|
156
|
+
raise ValueError(f"Unable to locate function {function_regex}")
|
|
157
|
+
|
|
158
|
+
# Handle address duplicates
|
|
159
|
+
for function, address in func_and_addr:
|
|
160
|
+
if address in addresses:
|
|
161
|
+
continue
|
|
162
|
+
addresses.add(address)
|
|
163
|
+
|
|
164
|
+
if probe_on_enter:
|
|
165
|
+
bpf_instance.attach_uprobe(name=path, sym=function, fn_name=bpf_fn_name)
|
|
166
|
+
if verbose:
|
|
167
|
+
print(f"Attaching to {function} at address {address} on enter")
|
|
168
|
+
else:
|
|
169
|
+
bpf_instance.attach_uretprobe(
|
|
170
|
+
name=path, sym=function, fn_name=bpf_fn_name
|
|
171
|
+
)
|
|
172
|
+
if verbose:
|
|
173
|
+
print(f"Attaching to {function} at address {address} on return")
|
pg_lock_tracer/pg_lock_tracer.py
CHANGED
|
@@ -95,7 +95,6 @@ parser.add_argument(
|
|
|
95
95
|
dest="pids",
|
|
96
96
|
metavar="PID",
|
|
97
97
|
help="the pid(s) to trace",
|
|
98
|
-
required=True,
|
|
99
98
|
)
|
|
100
99
|
parser.add_argument(
|
|
101
100
|
"-x",
|
|
@@ -352,7 +351,11 @@ class PGLockTraceOutputHuman(PGLockTraceOutput):
|
|
|
352
351
|
"""
|
|
353
352
|
event = self.bpf_instance["lockevents"].event(data)
|
|
354
353
|
|
|
355
|
-
if
|
|
354
|
+
if (
|
|
355
|
+
self.pids
|
|
356
|
+
and event.pid not in self.pids
|
|
357
|
+
and event.event_type < Events.GLOBAL
|
|
358
|
+
):
|
|
356
359
|
return
|
|
357
360
|
|
|
358
361
|
print_prefix = f"{event.timestamp} [Pid {event.pid}]"
|
|
@@ -593,18 +596,7 @@ class PGLockTracer:
|
|
|
593
596
|
self.oid_resolvers[resolver_pid] = oid_resolver
|
|
594
597
|
|
|
595
598
|
# Belong the processes to the binary?
|
|
596
|
-
|
|
597
|
-
if not os.path.isdir(f"/proc/{pid}"):
|
|
598
|
-
raise ValueError(
|
|
599
|
-
f"/proc entry for pid {pid} not found, does the process exist?"
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
binary = os.readlink(f"/proc/{pid}/exe")
|
|
603
|
-
|
|
604
|
-
if binary != self.args.path:
|
|
605
|
-
raise ValueError(
|
|
606
|
-
f"Pid {pid} does not belong to binary {self.args.path}. Executable is {binary}"
|
|
607
|
-
)
|
|
599
|
+
BPFHelper.check_pid_exe(self.args.pids, self.args.path)
|
|
608
600
|
|
|
609
601
|
# Does the output file already exists?
|
|
610
602
|
if self.args.output_file and os.path.exists(self.args.output_file):
|
|
@@ -692,91 +684,179 @@ class PGLockTracer:
|
|
|
692
684
|
|
|
693
685
|
# Open the event queue
|
|
694
686
|
self.bpf_instance["lockevents"].open_perf_buffer(
|
|
695
|
-
self.output_class.print_event, page_cnt=
|
|
687
|
+
self.output_class.print_event, page_cnt=BPFHelper.page_cnt
|
|
696
688
|
)
|
|
697
689
|
|
|
698
|
-
def register_probe(self, function_regex, bpf_fn_name, probe_on_enter=True):
|
|
699
|
-
"""
|
|
700
|
-
Register a BPF probe
|
|
701
|
-
"""
|
|
702
|
-
addresses = set()
|
|
703
|
-
func_and_addr = BPF.get_user_functions_and_addresses(
|
|
704
|
-
self.args.path, function_regex
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
if not func_and_addr:
|
|
708
|
-
raise ValueError(f"Unable to locate function {function_regex}")
|
|
709
|
-
|
|
710
|
-
# Handle address duplicates
|
|
711
|
-
for function, address in func_and_addr:
|
|
712
|
-
if address in addresses:
|
|
713
|
-
continue
|
|
714
|
-
addresses.add(address)
|
|
715
|
-
|
|
716
|
-
if probe_on_enter:
|
|
717
|
-
self.bpf_instance.attach_uprobe(
|
|
718
|
-
name=self.args.path, sym=function, fn_name=bpf_fn_name
|
|
719
|
-
)
|
|
720
|
-
if self.args.verbose:
|
|
721
|
-
print(f"Attaching to {function} at address {address} on enter")
|
|
722
|
-
else:
|
|
723
|
-
self.bpf_instance.attach_uretprobe(
|
|
724
|
-
name=self.args.path, sym=function, fn_name=bpf_fn_name
|
|
725
|
-
)
|
|
726
|
-
if self.args.verbose:
|
|
727
|
-
print(f"Attaching to {function} at address {address} on return")
|
|
728
|
-
|
|
729
690
|
def attach_probes(self):
|
|
730
691
|
"""
|
|
731
692
|
Attach BPF probes
|
|
732
693
|
"""
|
|
733
|
-
|
|
734
694
|
# Transaction probes
|
|
735
695
|
if self.args.trace is None or TraceEvents.TRANSACTION.name in self.args.trace:
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
696
|
+
BPFHelper.register_ebpf_probe(
|
|
697
|
+
self.args.path,
|
|
698
|
+
self.bpf_instance,
|
|
699
|
+
"^StartTransaction$",
|
|
700
|
+
"bpf_transaction_begin",
|
|
701
|
+
self.args.verbose,
|
|
702
|
+
)
|
|
703
|
+
BPFHelper.register_ebpf_probe(
|
|
704
|
+
self.args.path,
|
|
705
|
+
self.bpf_instance,
|
|
706
|
+
"^CommitTransaction$",
|
|
707
|
+
"bpf_transaction_commit",
|
|
708
|
+
self.args.verbose,
|
|
709
|
+
)
|
|
710
|
+
BPFHelper.register_ebpf_probe(
|
|
711
|
+
self.args.path,
|
|
712
|
+
self.bpf_instance,
|
|
713
|
+
"^AbortTransaction$",
|
|
714
|
+
"bpf_transaction_abort",
|
|
715
|
+
self.args.verbose,
|
|
716
|
+
)
|
|
717
|
+
BPFHelper.register_ebpf_probe(
|
|
718
|
+
self.args.path,
|
|
719
|
+
self.bpf_instance,
|
|
720
|
+
"^DeadLockReport$",
|
|
721
|
+
"bpf_deadlock",
|
|
722
|
+
self.args.verbose,
|
|
723
|
+
)
|
|
740
724
|
|
|
741
725
|
# Query probes
|
|
742
726
|
if self.args.trace is None or TraceEvents.QUERY.name in self.args.trace:
|
|
743
|
-
|
|
744
|
-
|
|
727
|
+
BPFHelper.register_ebpf_probe(
|
|
728
|
+
self.args.path,
|
|
729
|
+
self.bpf_instance,
|
|
730
|
+
"^exec_simple_query$",
|
|
731
|
+
"bpf_query_begin",
|
|
732
|
+
self.args.verbose,
|
|
733
|
+
)
|
|
734
|
+
BPFHelper.register_ebpf_probe(
|
|
735
|
+
self.args.path,
|
|
736
|
+
self.bpf_instance,
|
|
737
|
+
"^exec_simple_query$",
|
|
738
|
+
"bpf_query_end",
|
|
739
|
+
self.args.verbose,
|
|
740
|
+
False,
|
|
741
|
+
)
|
|
745
742
|
|
|
746
743
|
# Table probes
|
|
747
744
|
if self.args.trace is None or TraceEvents.TABLE.name in self.args.trace:
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
745
|
+
BPFHelper.register_ebpf_probe(
|
|
746
|
+
self.args.path,
|
|
747
|
+
self.bpf_instance,
|
|
748
|
+
"^table_open$",
|
|
749
|
+
"bpf_table_open",
|
|
750
|
+
self.args.verbose,
|
|
751
|
+
)
|
|
752
|
+
BPFHelper.register_ebpf_probe(
|
|
753
|
+
self.args.path,
|
|
754
|
+
self.bpf_instance,
|
|
755
|
+
"^table_openrv$",
|
|
756
|
+
"bpf_table_openrv",
|
|
757
|
+
self.args.verbose,
|
|
758
|
+
)
|
|
759
|
+
BPFHelper.register_ebpf_probe(
|
|
760
|
+
self.args.path,
|
|
761
|
+
self.bpf_instance,
|
|
762
|
+
"^table_openrv_extended$",
|
|
763
|
+
"bpf_table_openrv_extended",
|
|
764
|
+
self.args.verbose,
|
|
765
|
+
)
|
|
766
|
+
BPFHelper.register_ebpf_probe(
|
|
767
|
+
self.args.path,
|
|
768
|
+
self.bpf_instance,
|
|
769
|
+
"^table_close$",
|
|
770
|
+
"bpf_table_close",
|
|
771
|
+
self.args.verbose,
|
|
772
|
+
)
|
|
752
773
|
|
|
753
774
|
# Lock probes
|
|
754
775
|
if self.args.trace is None or TraceEvents.LOCK.name in self.args.trace:
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
776
|
+
BPFHelper.register_ebpf_probe(
|
|
777
|
+
self.args.path,
|
|
778
|
+
self.bpf_instance,
|
|
779
|
+
"^LockRelationOid$",
|
|
780
|
+
"bpf_lock_relation_oid",
|
|
781
|
+
self.args.verbose,
|
|
782
|
+
)
|
|
783
|
+
BPFHelper.register_ebpf_probe(
|
|
784
|
+
self.args.path,
|
|
785
|
+
self.bpf_instance,
|
|
786
|
+
"^LockRelationOid$",
|
|
787
|
+
"bpf_lock_relation_oid_end",
|
|
788
|
+
self.args.verbose,
|
|
789
|
+
False,
|
|
790
|
+
)
|
|
791
|
+
BPFHelper.register_ebpf_probe(
|
|
792
|
+
self.args.path,
|
|
793
|
+
self.bpf_instance,
|
|
794
|
+
"^UnlockRelationOid$",
|
|
795
|
+
"bpf_unlock_relation_oid",
|
|
796
|
+
self.args.verbose,
|
|
797
|
+
)
|
|
798
|
+
BPFHelper.register_ebpf_probe(
|
|
799
|
+
self.args.path,
|
|
800
|
+
self.bpf_instance,
|
|
801
|
+
"^GrantLock$",
|
|
802
|
+
"bpf_lock_grant",
|
|
803
|
+
self.args.verbose,
|
|
804
|
+
)
|
|
805
|
+
BPFHelper.register_ebpf_probe(
|
|
806
|
+
self.args.path,
|
|
807
|
+
self.bpf_instance,
|
|
760
808
|
"^FastPathGrantRelationLock$",
|
|
761
809
|
"bpf_lock_fastpath_grant",
|
|
810
|
+
self.args.verbose,
|
|
762
811
|
)
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
812
|
+
BPFHelper.register_ebpf_probe(
|
|
813
|
+
self.args.path,
|
|
814
|
+
self.bpf_instance,
|
|
815
|
+
"^GrantLockLocal$",
|
|
816
|
+
"bpf_lock_local_grant",
|
|
817
|
+
self.args.verbose,
|
|
818
|
+
)
|
|
819
|
+
BPFHelper.register_ebpf_probe(
|
|
820
|
+
self.args.path,
|
|
821
|
+
self.bpf_instance,
|
|
822
|
+
"^UnGrantLock$",
|
|
823
|
+
"bpf_lock_ungrant",
|
|
824
|
+
self.args.verbose,
|
|
825
|
+
)
|
|
826
|
+
BPFHelper.register_ebpf_probe(
|
|
827
|
+
self.args.path,
|
|
828
|
+
self.bpf_instance,
|
|
766
829
|
"^FastPathUnGrantRelationLock$",
|
|
767
830
|
"bpf_lock_fastpath_ungrant",
|
|
831
|
+
self.args.verbose,
|
|
832
|
+
)
|
|
833
|
+
BPFHelper.register_ebpf_probe(
|
|
834
|
+
self.args.path,
|
|
835
|
+
self.bpf_instance,
|
|
836
|
+
"^RemoveLocalLock$",
|
|
837
|
+
"bfp_local_lock_ungrant",
|
|
838
|
+
self.args.verbose,
|
|
768
839
|
)
|
|
769
|
-
self.register_probe("^RemoveLocalLock$", "bfp_local_lock_ungrant")
|
|
770
840
|
|
|
771
841
|
# Invalidation messages probes
|
|
772
842
|
if self.args.trace is None or TraceEvents.INVALIDATION.name in self.args.trace:
|
|
773
|
-
|
|
774
|
-
|
|
843
|
+
BPFHelper.register_ebpf_probe(
|
|
844
|
+
self.args.path,
|
|
845
|
+
self.bpf_instance,
|
|
846
|
+
"^AcceptInvalidationMessages$",
|
|
847
|
+
"bpf_accept_invalidation_messages",
|
|
848
|
+
self.args.verbose,
|
|
775
849
|
)
|
|
776
850
|
|
|
777
851
|
# Error probes
|
|
778
852
|
if self.args.trace is None or TraceEvents.ERROR.name in self.args.trace:
|
|
779
|
-
|
|
853
|
+
BPFHelper.register_ebpf_probe(
|
|
854
|
+
self.args.path,
|
|
855
|
+
self.bpf_instance,
|
|
856
|
+
"^errstart$",
|
|
857
|
+
"bpf_errstart",
|
|
858
|
+
self.args.verbose,
|
|
859
|
+
)
|
|
780
860
|
|
|
781
861
|
def run(self):
|
|
782
862
|
"""
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import sys
|
|
13
13
|
import argparse
|
|
14
14
|
|
|
15
|
-
from enum import IntEnum
|
|
15
|
+
from enum import IntEnum, unique
|
|
16
16
|
|
|
17
17
|
from bcc import BPF, USDT
|
|
18
18
|
from prettytable import PrettyTable
|
|
@@ -65,6 +65,7 @@ parser.add_argument(
|
|
|
65
65
|
parser.add_argument("--statistics", action="store_true", help="print lock statistics")
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
@unique
|
|
68
69
|
class Events(IntEnum):
|
|
69
70
|
LOCK = 0
|
|
70
71
|
LOCK_OR_WAIT = 1
|
|
@@ -76,6 +77,13 @@ class Events(IntEnum):
|
|
|
76
77
|
COND_ACQUIRE_FAIL = 7
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
@unique
|
|
81
|
+
class LWLockMode(IntEnum):
|
|
82
|
+
LW_EXCLUSIVE = 0
|
|
83
|
+
LW_SHARED = 1
|
|
84
|
+
LW_WAIT_UNTIL_FREE = 2
|
|
85
|
+
|
|
86
|
+
|
|
79
87
|
class LockStatisticsEntry:
|
|
80
88
|
def __init__(self) -> None:
|
|
81
89
|
# The number of non-waited requested locks
|
|
@@ -239,22 +247,6 @@ class PGLWLockTracer:
|
|
|
239
247
|
|
|
240
248
|
return event.timestamp - self.last_lock_request_time[event.pid]
|
|
241
249
|
|
|
242
|
-
@staticmethod
|
|
243
|
-
def resolve_lock_mode(event):
|
|
244
|
-
"""
|
|
245
|
-
Resolve the LW Lock modes
|
|
246
|
-
"""
|
|
247
|
-
if event.mode == 0: # LW_EXCLUSIVE,
|
|
248
|
-
return "LW_EXCLUSIVE"
|
|
249
|
-
|
|
250
|
-
if event.mode == 1: # LW_SHARED
|
|
251
|
-
return "LW_SHARED"
|
|
252
|
-
|
|
253
|
-
if event.mode == 2: # LW_WAIT_UNTIL_FREE
|
|
254
|
-
return "LW_WAIT_UNTIL_FREE"
|
|
255
|
-
|
|
256
|
-
raise ValueError(f"Unknown event type {event.event_type}")
|
|
257
|
-
|
|
258
250
|
def print_lock_event(self, _cpu, data, _size):
|
|
259
251
|
"""
|
|
260
252
|
Print a new lock event.
|
|
@@ -267,7 +259,7 @@ class PGLWLockTracer:
|
|
|
267
259
|
tranche = event.tranche.decode("utf-8")
|
|
268
260
|
|
|
269
261
|
print_prefix = f"{event.timestamp} [Pid {event.pid}]"
|
|
270
|
-
lock_mode =
|
|
262
|
+
lock_mode = LWLockMode(event.mode).name
|
|
271
263
|
|
|
272
264
|
self.update_statistics(event, tranche, lock_mode)
|
|
273
265
|
|
|
@@ -331,6 +323,7 @@ class PGLWLockTracer:
|
|
|
331
323
|
print("=======")
|
|
332
324
|
|
|
333
325
|
enum_defines = BPFHelper.enum_to_defines(Events, "EVENT")
|
|
326
|
+
|
|
334
327
|
bpf_program = BPFHelper.read_bpf_program("pg_lw_lock_tracer.c")
|
|
335
328
|
bpf_program_final = bpf_program.replace("__DEFINES__", enum_defines)
|
|
336
329
|
|
|
@@ -347,7 +340,7 @@ class PGLWLockTracer:
|
|
|
347
340
|
)
|
|
348
341
|
|
|
349
342
|
self.bpf_instance["lockevents"].open_perf_buffer(
|
|
350
|
-
self.print_lock_event, page_cnt=
|
|
343
|
+
self.print_lock_event, page_cnt=BPFHelper.page_cnt
|
|
351
344
|
)
|
|
352
345
|
|
|
353
346
|
def print_statistics(self):
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# PostgreSQL row lock tracer.
|
|
4
|
+
#
|
|
5
|
+
# See https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS
|
|
6
|
+
#
|
|
7
|
+
###############################################
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
from enum import IntEnum, unique
|
|
13
|
+
|
|
14
|
+
from bcc import BPF
|
|
15
|
+
from prettytable import PrettyTable
|
|
16
|
+
|
|
17
|
+
from pg_lock_tracer import __version__
|
|
18
|
+
from pg_lock_tracer.helper import BPFHelper
|
|
19
|
+
|
|
20
|
+
EXAMPLES = """examples:
|
|
21
|
+
# Trace the row locks of the given PostgreSQL binary
|
|
22
|
+
pg_row_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
23
|
+
|
|
24
|
+
# Trace the row locks of the PID 1234
|
|
25
|
+
pg_row_lock_tracer -p 1234 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
26
|
+
|
|
27
|
+
# Trace the row locks of the PID 1234 and 5678
|
|
28
|
+
pg_row_lock_tracer -p 1234 -p 5678 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
29
|
+
|
|
30
|
+
# Trace the row locks of the PID 1234 and be verbose
|
|
31
|
+
pg_row_lock_tracer -p 1234 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres -v
|
|
32
|
+
|
|
33
|
+
# Trace the row locks and show statistics
|
|
34
|
+
pg_row_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres --statistics
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
parser = argparse.ArgumentParser(
|
|
38
|
+
description="",
|
|
39
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
40
|
+
epilog=EXAMPLES,
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"-V",
|
|
44
|
+
"--version",
|
|
45
|
+
action="version",
|
|
46
|
+
version=f"{parser.prog} ({__version__})",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Be verbose")
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"-p",
|
|
51
|
+
"--pid",
|
|
52
|
+
type=int,
|
|
53
|
+
nargs="+",
|
|
54
|
+
dest="pids",
|
|
55
|
+
metavar="PID",
|
|
56
|
+
help="the pid(s) to trace",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"-x",
|
|
60
|
+
"--exe",
|
|
61
|
+
type=str,
|
|
62
|
+
required=True,
|
|
63
|
+
dest="path",
|
|
64
|
+
metavar="PATH",
|
|
65
|
+
help="path to binary",
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"-d",
|
|
69
|
+
"--dry-run",
|
|
70
|
+
action="store_true",
|
|
71
|
+
help="compile and load the BPF program but exit afterward",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument("--statistics", action="store_true", help="print lock statistics")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@unique
|
|
77
|
+
class Events(IntEnum):
|
|
78
|
+
LOCK_TUPLE = 0
|
|
79
|
+
LOCK_TUPLE_END = 1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# See lockoptions.h in PostgreSQL
|
|
83
|
+
@unique
|
|
84
|
+
class TMResult(IntEnum):
|
|
85
|
+
TM_OK = 0
|
|
86
|
+
TM_INVISIBLE = 1
|
|
87
|
+
TM_SELFMODIFIED = 2
|
|
88
|
+
TM_UPDATED = 3
|
|
89
|
+
TM_DELETED = 4
|
|
90
|
+
TM_BEINGMODIFIED = 5
|
|
91
|
+
TM_WOULDBLOCK = 6
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# See lockoptions.h in PostgreSQL
|
|
95
|
+
@unique
|
|
96
|
+
class LockWaitPolicy(IntEnum):
|
|
97
|
+
LOCK_WAIT_BLOCK = 0
|
|
98
|
+
LOCK_WAIT_SKIP = 1
|
|
99
|
+
LOCK_WAIT_ERROR = 2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# See lockoptions.h in PostgreSQL
|
|
103
|
+
@unique
|
|
104
|
+
class LockTupleMode(IntEnum):
|
|
105
|
+
LOCK_TUPLE_KEYSHARE = 0
|
|
106
|
+
LOCK_TUPLE_SHARE = 1
|
|
107
|
+
LOCK_TUPLE_NOKEYEXCLUSIVE = 2
|
|
108
|
+
LOCK_TUPLE_EXCLUSIVE = 3
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class LockStatisticsEntry:
|
|
112
|
+
def __init__(self) -> None:
|
|
113
|
+
# The requested locks
|
|
114
|
+
self._lock_modes = {}
|
|
115
|
+
|
|
116
|
+
# The used lock policies
|
|
117
|
+
self._lock_policies = {}
|
|
118
|
+
|
|
119
|
+
# The lock results
|
|
120
|
+
self._lock_results = {}
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def lock_modes(self):
|
|
124
|
+
return self._lock_modes
|
|
125
|
+
|
|
126
|
+
@lock_modes.setter
|
|
127
|
+
def lock_modes(self, value):
|
|
128
|
+
self._lock_modes = value
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def lock_policies(self):
|
|
132
|
+
return self._lock_policies
|
|
133
|
+
|
|
134
|
+
@lock_policies.setter
|
|
135
|
+
def lock_policies(self, value):
|
|
136
|
+
self._lock_policies = value
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def lock_results(self):
|
|
140
|
+
return self._lock_results
|
|
141
|
+
|
|
142
|
+
@lock_results.setter
|
|
143
|
+
def lock_results(self, value):
|
|
144
|
+
self._results = value
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class PGRowLockTracer:
|
|
148
|
+
def __init__(self, prog_args):
|
|
149
|
+
self.bpf_instance = None
|
|
150
|
+
self.args = prog_args
|
|
151
|
+
self.statistics = {}
|
|
152
|
+
|
|
153
|
+
# Variables for lock timing
|
|
154
|
+
self.last_lock_request_time = {}
|
|
155
|
+
|
|
156
|
+
# Belong the processes to the binary?
|
|
157
|
+
BPFHelper.check_pid_exe(self.args.pids, self.args.path)
|
|
158
|
+
|
|
159
|
+
def get_lock_wait_time(self, event):
|
|
160
|
+
"""
|
|
161
|
+
Get the last lock wait time (WAIT_START updates
|
|
162
|
+
last_lock_request_time).
|
|
163
|
+
"""
|
|
164
|
+
if event.event_type != Events.LOCK_TUPLE_END:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
return event.timestamp - self.last_lock_request_time[event.pid]
|
|
168
|
+
|
|
169
|
+
def update_statistics(self, event):
|
|
170
|
+
"""
|
|
171
|
+
Update the statistics
|
|
172
|
+
"""
|
|
173
|
+
if event.pid not in self.statistics:
|
|
174
|
+
self.statistics[event.pid] = LockStatisticsEntry()
|
|
175
|
+
|
|
176
|
+
statistics_entry = self.statistics.get(event.pid)
|
|
177
|
+
|
|
178
|
+
# Lock requested
|
|
179
|
+
if event.event_type == Events.LOCK_TUPLE:
|
|
180
|
+
lock_wait_policy = LockWaitPolicy(event.lockwaitpolicy)
|
|
181
|
+
|
|
182
|
+
if lock_wait_policy in statistics_entry.lock_policies:
|
|
183
|
+
statistics_entry.lock_policies[lock_wait_policy] += 1
|
|
184
|
+
else:
|
|
185
|
+
statistics_entry.lock_policies[lock_wait_policy] = 1
|
|
186
|
+
|
|
187
|
+
lock_tuple_mode = LockTupleMode(event.locktuplemode)
|
|
188
|
+
|
|
189
|
+
if lock_tuple_mode in statistics_entry.lock_modes:
|
|
190
|
+
statistics_entry.lock_modes[lock_tuple_mode] += 1
|
|
191
|
+
else:
|
|
192
|
+
statistics_entry.lock_modes[lock_tuple_mode] = 1
|
|
193
|
+
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Lock request done
|
|
197
|
+
if event.event_type == Events.LOCK_TUPLE_END:
|
|
198
|
+
lock_result = TMResult(event.lockresult)
|
|
199
|
+
|
|
200
|
+
if lock_result in statistics_entry.lock_results:
|
|
201
|
+
statistics_entry.lock_results[lock_result] += 1
|
|
202
|
+
else:
|
|
203
|
+
statistics_entry.lock_results[lock_result] = 1
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
def print_lock_event(self, _cpu, data, _size):
|
|
209
|
+
"""
|
|
210
|
+
Print a new lock event.
|
|
211
|
+
"""
|
|
212
|
+
event = self.bpf_instance["lockevents"].event(data)
|
|
213
|
+
|
|
214
|
+
if self.args.pids and event.pid not in self.args.pids:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
print_prefix = f"{event.timestamp} [Pid {event.pid}]"
|
|
218
|
+
|
|
219
|
+
self.update_statistics(event)
|
|
220
|
+
|
|
221
|
+
if event.event_type == Events.LOCK_TUPLE:
|
|
222
|
+
self.last_lock_request_time[event.pid] = event.timestamp
|
|
223
|
+
|
|
224
|
+
locktuplemode = LockTupleMode(event.locktuplemode).name
|
|
225
|
+
lockwaitpolicy = LockWaitPolicy(event.lockwaitpolicy).name
|
|
226
|
+
|
|
227
|
+
print(
|
|
228
|
+
f"{print_prefix} LOCK_TUPLE (Tablespace {event.tablespace} "
|
|
229
|
+
f"database {event.database} relation {event.relation}) "
|
|
230
|
+
f"- (Block and offset {event.blockid} {event.offset}) "
|
|
231
|
+
f"- {locktuplemode} {lockwaitpolicy}"
|
|
232
|
+
)
|
|
233
|
+
elif event.event_type == Events.LOCK_TUPLE_END:
|
|
234
|
+
lockresult = TMResult(event.lockresult).name
|
|
235
|
+
needed_time = self.get_lock_wait_time(event)
|
|
236
|
+
print(f"{print_prefix} LOCK_TUPLE_END {lockresult} in {needed_time} ns")
|
|
237
|
+
else:
|
|
238
|
+
raise ValueError(f"Unknown event type {event.event_type}")
|
|
239
|
+
|
|
240
|
+
def init(self):
|
|
241
|
+
"""
|
|
242
|
+
Init the PostgreSQL lock tracer
|
|
243
|
+
"""
|
|
244
|
+
enum_defines = BPFHelper.enum_to_defines(Events, "EVENT")
|
|
245
|
+
|
|
246
|
+
bpf_program = BPFHelper.read_bpf_program("pg_row_lock_tracer.c")
|
|
247
|
+
bpf_program_final = bpf_program.replace("__DEFINES__", enum_defines)
|
|
248
|
+
|
|
249
|
+
if self.args.verbose:
|
|
250
|
+
print(bpf_program_final)
|
|
251
|
+
|
|
252
|
+
# Disable warnings like
|
|
253
|
+
# 'warning: '__HAVE_BUILTIN_BSWAP32__' macro redefined [-Wmacro-redefined]'
|
|
254
|
+
bpf_cflags = ["-Wno-macro-redefined"] if not self.args.verbose else []
|
|
255
|
+
|
|
256
|
+
print("===> Compiling BPF program")
|
|
257
|
+
self.bpf_instance = BPF(text=bpf_program_final, cflags=bpf_cflags)
|
|
258
|
+
|
|
259
|
+
print("===> Attaching BPF probes")
|
|
260
|
+
self.attach_probes()
|
|
261
|
+
|
|
262
|
+
# Open the event queue
|
|
263
|
+
self.bpf_instance["lockevents"].open_perf_buffer(
|
|
264
|
+
self.print_lock_event, page_cnt=BPFHelper.page_cnt
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def attach_probes(self):
|
|
268
|
+
"""
|
|
269
|
+
Attach BPF probes
|
|
270
|
+
"""
|
|
271
|
+
BPFHelper.register_ebpf_probe(
|
|
272
|
+
self.args.path,
|
|
273
|
+
self.bpf_instance,
|
|
274
|
+
"^heapam_tuple_lock$",
|
|
275
|
+
"heapam_tuple_lock",
|
|
276
|
+
self.args.verbose,
|
|
277
|
+
)
|
|
278
|
+
BPFHelper.register_ebpf_probe(
|
|
279
|
+
self.args.path,
|
|
280
|
+
self.bpf_instance,
|
|
281
|
+
"^heapam_tuple_lock$",
|
|
282
|
+
"heapam_tuple_lock_end",
|
|
283
|
+
self.args.verbose,
|
|
284
|
+
False,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def print_statistics(self):
|
|
288
|
+
"""
|
|
289
|
+
Print lock statistics
|
|
290
|
+
"""
|
|
291
|
+
print("\nLock statistics:\n================")
|
|
292
|
+
|
|
293
|
+
# Wait policies
|
|
294
|
+
print("\nUsed wait policies:")
|
|
295
|
+
wait_polices = ["PID"]
|
|
296
|
+
|
|
297
|
+
for wait_policy in LockWaitPolicy:
|
|
298
|
+
wait_polices.append(wait_policy.name)
|
|
299
|
+
|
|
300
|
+
table = PrettyTable(wait_polices)
|
|
301
|
+
|
|
302
|
+
for pid in sorted(self.statistics):
|
|
303
|
+
statistics = self.statistics[pid]
|
|
304
|
+
pid_statistics = [pid]
|
|
305
|
+
for wait_policy in LockWaitPolicy:
|
|
306
|
+
pid_statistics.append(statistics.lock_policies.get(wait_policy, 0))
|
|
307
|
+
table.add_row(pid_statistics)
|
|
308
|
+
print(table)
|
|
309
|
+
|
|
310
|
+
# Lock modes
|
|
311
|
+
print("\nLock modes:")
|
|
312
|
+
lock_modes = ["PID"]
|
|
313
|
+
|
|
314
|
+
for lock_mode in LockTupleMode:
|
|
315
|
+
lock_modes.append(lock_mode.name)
|
|
316
|
+
|
|
317
|
+
table = PrettyTable(lock_modes)
|
|
318
|
+
|
|
319
|
+
for pid in sorted(self.statistics):
|
|
320
|
+
statistics = self.statistics[pid]
|
|
321
|
+
pid_statistics = [pid]
|
|
322
|
+
for lock_mode in LockTupleMode:
|
|
323
|
+
pid_statistics.append(statistics.lock_modes.get(lock_mode, 0))
|
|
324
|
+
table.add_row(pid_statistics)
|
|
325
|
+
print(table)
|
|
326
|
+
|
|
327
|
+
# Lock results
|
|
328
|
+
print("\nLock results:")
|
|
329
|
+
lock_results = ["PID"]
|
|
330
|
+
|
|
331
|
+
for lock_result in TMResult:
|
|
332
|
+
lock_results.append(lock_result.name)
|
|
333
|
+
|
|
334
|
+
table = PrettyTable(lock_results)
|
|
335
|
+
|
|
336
|
+
for pid in sorted(self.statistics):
|
|
337
|
+
statistics = self.statistics[pid]
|
|
338
|
+
pid_statistics = [pid]
|
|
339
|
+
for lock_result in TMResult:
|
|
340
|
+
pid_statistics.append(statistics.lock_results.get(lock_result, 0))
|
|
341
|
+
table.add_row(pid_statistics)
|
|
342
|
+
print(table)
|
|
343
|
+
|
|
344
|
+
def run(self):
|
|
345
|
+
"""
|
|
346
|
+
Run the BPF program and read results
|
|
347
|
+
"""
|
|
348
|
+
print("===> Ready to trace")
|
|
349
|
+
while True:
|
|
350
|
+
try:
|
|
351
|
+
self.bpf_instance.perf_buffer_poll()
|
|
352
|
+
except KeyboardInterrupt:
|
|
353
|
+
if self.args.statistics:
|
|
354
|
+
self.print_statistics()
|
|
355
|
+
sys.exit(0)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def main():
|
|
359
|
+
"""
|
|
360
|
+
Entry point for the BPF based PostgreSQL row lock tracer.
|
|
361
|
+
"""
|
|
362
|
+
args = parser.parse_args()
|
|
363
|
+
|
|
364
|
+
pg_lock_tracer = PGRowLockTracer(args)
|
|
365
|
+
pg_lock_tracer.init()
|
|
366
|
+
|
|
367
|
+
if not args.dry_run:
|
|
368
|
+
pg_lock_tracer.run()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if __name__ == "__main__":
|
|
372
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.
|
|
2
|
+
Name: pg_lock_tracer
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A BPF based lock tracer for the PostgreSQL database
|
|
5
5
|
Home-page: https://github.com/jnidzwetzki/pg-lock-tracer
|
|
6
6
|
Author: Jan Nidzwetzki
|
|
@@ -30,11 +30,12 @@ Requires-Dist: psycopg2
|
|
|
30
30
|
|
|
31
31
|
This project provides tools that allow you to gain deep insights into PostgreSQL's locking activities and troubleshoot locking-related issues (e.g., performance problems or deadlocks).
|
|
32
32
|
|
|
33
|
-
* `pg_lock_tracer` - is a lock tracer
|
|
34
|
-
* `pg_lw_lock_tracer` -
|
|
33
|
+
* `pg_lock_tracer` - is a PostgreSQL table level lock tracer.
|
|
34
|
+
* `pg_lw_lock_tracer` - is a tracer for PostgreSQL lightweight locks (LWLocks).
|
|
35
|
+
* `pg_row_lock_tracer` - is a tracer for PostgreSQL row locks.
|
|
35
36
|
* `animate_lock_graph` - creates animated locks graphs based on the `pg_lock_tracer` output.
|
|
36
37
|
|
|
37
|
-
__Note:__
|
|
38
|
+
__Note:__ These tools employ the [eBPF](https://ebpf.io/) (_Extended Berkeley Packet Filter_) technology. At the moment, PostgreSQL 12, 13, 14, 15, and 16 are supported (see additional information below).
|
|
38
39
|
|
|
39
40
|
# pg_lock_tracer
|
|
40
41
|
`pg_lock_tracer` observes the locking activity of a running PostgreSQL process (using _eBPF_ and _UProbes_). In contrast to the information that is present in the table `pg_locks` (which provides information about which locks are _currently_ requested), `pg_lock_tracer` gives you a continuous view of the locking activity and collects statistics and timings.
|
|
@@ -686,7 +687,7 @@ pg_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_2_DEBUG/bin/postgres -
|
|
|
686
687
|
### Animated Lock Graphs
|
|
687
688
|
See the content of the [examples](examples/) directory for examples.
|
|
688
689
|
|
|
689
|
-
#
|
|
690
|
+
# pg_lw_lock_tracer
|
|
690
691
|
|
|
691
692
|
`pg_lw_lock_trace` allows to trace lightweight locks ([LWLocks](https://github.com/postgres/postgres/blob/c8e1ba736b2b9e8c98d37a5b77c4ed31baf94147/src/backend/storage/lmgr/lwlock.c)) in a PostgreSQL process via _Userland Statically Defined Tracing_ (USDT).
|
|
692
693
|
|
|
@@ -805,8 +806,73 @@ Locks per type
|
|
|
805
806
|
+--------------+----------+
|
|
806
807
|
```
|
|
807
808
|
|
|
808
|
-
#
|
|
809
|
+
# pg_row_lock_tracer
|
|
810
|
+
|
|
811
|
+
`pg_row_lock_tracer` allows to trace row locks (see the PostgreSQL [documentation](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS)) of a PostgreSQL process using _eBPF_ and _UProbes_
|
|
812
|
+
|
|
813
|
+
## Usage Examples
|
|
814
|
+
```
|
|
815
|
+
# Trace the row locks of the given PostgreSQL binary
|
|
816
|
+
pg_row_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
817
|
+
|
|
818
|
+
# Trace the row locks of the PID 1234
|
|
819
|
+
pg_row_lock_tracer -p 1234 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
820
|
+
|
|
821
|
+
# Trace the row locks of the PID 1234 and 5678
|
|
822
|
+
pg_row_lock_tracer -p 1234 -p 5678 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres
|
|
823
|
+
|
|
824
|
+
# Trace the row locks of the PID 1234 and be verbose
|
|
825
|
+
pg_row_lock_tracer -p 1234 -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres -v
|
|
826
|
+
|
|
827
|
+
# Trace the row locks and show statistics
|
|
828
|
+
pg_row_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres --statistics
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
## Example output
|
|
832
|
+
|
|
833
|
+
SQL Query: `SELECT * FROM temperature FOR UPDATE;`
|
|
809
834
|
|
|
835
|
+
CLI: `sudo pg_row_lock_tracer -x /home/jan/postgresql-sandbox/bin/REL_14_9_DEBUG/bin/postgres --statistics`
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
Tracer output:
|
|
839
|
+
|
|
840
|
+
```
|
|
841
|
+
[...]
|
|
842
|
+
2783502701862408 [Pid 2604491] LOCK_TUPLE_END TM_OK in 13100 ns
|
|
843
|
+
2783502701877081 [Pid 2604491] LOCK_TUPLE (Tablespace 1663 database 305234 relation 313419) - (Block and offset 7 143) - LOCK_TUPLE_EXCLUSIVE LOCK_WAIT_BLOCK
|
|
844
|
+
2783502701972367 [Pid 2604491] LOCK_TUPLE_END TM_OK in 95286 ns
|
|
845
|
+
2783502701988387 [Pid 2604491] LOCK_TUPLE (Tablespace 1663 database 305234 relation 313419) - (Block and offset 7 144) - LOCK_TUPLE_EXCLUSIVE LOCK_WAIT_BLOCK
|
|
846
|
+
2783502702001690 [Pid 2604491] LOCK_TUPLE_END TM_OK in 13303 ns
|
|
847
|
+
2783502702016387 [Pid 2604491] LOCK_TUPLE (Tablespace 1663 database 305234 relation 313419) - (Block and offset 7 145) - LOCK_TUPLE_EXCLUSIVE LOCK_WAIT_BLOCK
|
|
848
|
+
2783502702029375 [Pid 2604491] LOCK_TUPLE_END TM_OK in 12988 ns
|
|
849
|
+
^C
|
|
850
|
+
Lock statistics:
|
|
851
|
+
================
|
|
852
|
+
|
|
853
|
+
Used wait policies:
|
|
854
|
+
+---------+-----------------+----------------+-----------------+
|
|
855
|
+
| PID | LOCK_WAIT_BLOCK | LOCK_WAIT_SKIP | LOCK_WAIT_ERROR |
|
|
856
|
+
+---------+-----------------+----------------+-----------------+
|
|
857
|
+
| 2604491 | 1440 | 0 | 0 |
|
|
858
|
+
+---------+-----------------+----------------+-----------------+
|
|
859
|
+
|
|
860
|
+
Lock modes:
|
|
861
|
+
+---------+---------------------+------------------+---------------------------+----------------------+
|
|
862
|
+
| PID | LOCK_TUPLE_KEYSHARE | LOCK_TUPLE_SHARE | LOCK_TUPLE_NOKEYEXCLUSIVE | LOCK_TUPLE_EXCLUSIVE |
|
|
863
|
+
+---------+---------------------+------------------+---------------------------+----------------------+
|
|
864
|
+
| 2604491 | 0 | 0 | 0 | 1440 |
|
|
865
|
+
+---------+---------------------+------------------+---------------------------+----------------------+
|
|
866
|
+
|
|
867
|
+
Lock results:
|
|
868
|
+
+---------+-------+--------------+-----------------+------------+------------+------------------+---------------+
|
|
869
|
+
| PID | TM_OK | TM_INVISIBLE | TM_SELFMODIFIED | TM_UPDATED | TM_DELETED | TM_BEINGMODIFIED | TM_WOULDBLOCK |
|
|
870
|
+
+---------+-------+--------------+-----------------+------------+------------+------------------+---------------+
|
|
871
|
+
| 2604491 | 1440 | 0 | 0 | 0 | 0 | 0 | 0 |
|
|
872
|
+
+---------+-------+--------------+-----------------+------------+------------+------------------+---------------+
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
# Additional Information
|
|
810
876
|
|
|
811
877
|
## Installation
|
|
812
878
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pg_lock_tracer/__init__.py,sha256=cID1jLnC_vj48GgMN6Yb1FA3JsQ95zNmCHmRYE8TFhY,22
|
|
2
|
+
pg_lock_tracer/animate_lock_graph.py,sha256=ohRKBlKILx7f2I0sBC26UYEG7mIbrNcDttmIwR8Aokk,12033
|
|
3
|
+
pg_lock_tracer/helper.py,sha256=4l_H6rCXShPu-snBA_OqCgKTzHUYEbgWK3zhmi6WJ9M,5271
|
|
4
|
+
pg_lock_tracer/oid_resolver.py,sha256=K0oF7SRZfqm092Tc_pZ-RlCpfHtnspwhjX1meTsU8Fk,3534
|
|
5
|
+
pg_lock_tracer/pg_lock_tracer.py,sha256=OohbLE1xSUbHiU3lSGJYACUgAVtVoK1V0Ew7kD_smlQ,30060
|
|
6
|
+
pg_lock_tracer/pg_lw_lock_tracer.py,sha256=Jgsr9ArWQigj-ndeAKEObVIYyNIaTKNGIkDrtA8sj9w,13204
|
|
7
|
+
pg_lock_tracer/pg_row_lock_tracer.py,sha256=duQalO2vf-QX4WRFd6K0WRunFlZbTHZeVkeWkr35zbo,10405
|
|
8
|
+
pg_lock_tracer/bpf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pg_lock_tracer/bpf/pg_lock_tracer.c,sha256=EM9gFZtkeBROIM692s-DrD-xlW0khFRNLXPN5_nBFBg,14003
|
|
10
|
+
pg_lock_tracer/bpf/pg_lw_lock_tracer.c,sha256=BxEpEOCo7hLJSOjcsKkng3kZhYJ80A_6k-9nxaJ6e2A,4916
|
|
11
|
+
pg_lock_tracer/bpf/pg_row_lock_tracer.c,sha256=U2Uqtv68qi72bJCVNHdpO68QTVJzhUFRATHX4hCWzDY,3344
|
|
12
|
+
pg_lock_tracer-0.6.0.dist-info/LICENSE,sha256=opL3U54Vdmg02lhP1XzHATC9Fyy393DcZBi-HqotSlQ,11348
|
|
13
|
+
pg_lock_tracer-0.6.0.dist-info/METADATA,sha256=1Hc_aUDwBCatzVTNJVBsev9FAmSPN1pkQrFkMrbNatE,73024
|
|
14
|
+
pg_lock_tracer-0.6.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
15
|
+
pg_lock_tracer-0.6.0.dist-info/entry_points.txt,sha256=kCKw8Zbn3YXKdu8mBeVzIzOmGoLjD-5D40CZg8-_W9c,248
|
|
16
|
+
pg_lock_tracer-0.6.0.dist-info/top_level.txt,sha256=nRoLeqWT0GSR4-JDcVRuI5ltF69rs7ftfkvImNbiR1Q,15
|
|
17
|
+
pg_lock_tracer-0.6.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pg_lock_tracer/__init__.py,sha256=DITpct-LrdIsTgwx2NgH5Ghx5y8Xgz1YMimy1ZV5RTY,22
|
|
2
|
-
pg_lock_tracer/animate_lock_graph.py,sha256=ohRKBlKILx7f2I0sBC26UYEG7mIbrNcDttmIwR8Aokk,12033
|
|
3
|
-
pg_lock_tracer/helper.py,sha256=bWxYVa65q6DXBhR9PIE3DVaZ2u08rsLehnluPRsOH3Y,3475
|
|
4
|
-
pg_lock_tracer/oid_resolver.py,sha256=K0oF7SRZfqm092Tc_pZ-RlCpfHtnspwhjX1meTsU8Fk,3534
|
|
5
|
-
pg_lock_tracer/pg_lock_tracer.py,sha256=KSkF7onGy6M5PYjuIxxc3GMUFXqcAebROot6_yg2rxw,28308
|
|
6
|
-
pg_lock_tracer/pg_lw_lock_tracer.py,sha256=77y_vKdLoyNPigkb8cd9WOXGrccStUUGcJV4iS_uFgE,13502
|
|
7
|
-
pg_lock_tracer/bpf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pg_lock_tracer/bpf/pg_lock_tracer.c,sha256=EM9gFZtkeBROIM692s-DrD-xlW0khFRNLXPN5_nBFBg,14003
|
|
9
|
-
pg_lock_tracer/bpf/pg_lw_lock_tracer.c,sha256=BxEpEOCo7hLJSOjcsKkng3kZhYJ80A_6k-9nxaJ6e2A,4916
|
|
10
|
-
pg_lock_tracer-0.5.4.dist-info/LICENSE,sha256=opL3U54Vdmg02lhP1XzHATC9Fyy393DcZBi-HqotSlQ,11348
|
|
11
|
-
pg_lock_tracer-0.5.4.dist-info/METADATA,sha256=J4WvNj_ryoN1kpku3LhAaxsZntotlOPFMlJf2ogOF7s,69560
|
|
12
|
-
pg_lock_tracer-0.5.4.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
13
|
-
pg_lock_tracer-0.5.4.dist-info/entry_points.txt,sha256=D5A-ZxfF80GaiGtUFQyvmXrJIxLn-CcIIw8G4E95y0c,188
|
|
14
|
-
pg_lock_tracer-0.5.4.dist-info/top_level.txt,sha256=nRoLeqWT0GSR4-JDcVRuI5ltF69rs7ftfkvImNbiR1Q,15
|
|
15
|
-
pg_lock_tracer-0.5.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|