-
Notifications
You must be signed in to change notification settings - Fork 0
/
tree_lock_wait.py
executable file
·191 lines (165 loc) · 4.9 KB
/
tree_lock_wait.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/python2
# @lint-avoid-python-3-compatibility-imports
#
# # ./tree_lock_wait.py [-t <time_interval>] [-f <fsid>]
#
# <time_interval>: Sampling time interval, either in ns
# or with unit like "100ms" (default value)
# <fsid>: Only catch events from fsid
#
# output will be csv format.
from __future__ import print_function
from bcc import BPF
from sys import stderr
from sys import argv
from collections import defaultdict
import uuid
import binascii
import getopt
text_bpf = '''
#include <uapi/linux/ptrace.h>
#define FSID_SIZE 16
struct data_t {
u8 fsid[FSID_SIZE];
u64 start_ns;
u64 end_ns;
u64 owner;
};
BPF_PERF_OUTPUT(events);
TRACEPOINT_PROBE(btrfs, btrfs_tree_read_lock)
{
struct data_t data;
bpf_probe_read(data.fsid, FSID_SIZE, args->fsid);
data.start_ns = args->start_ns;
data.end_ns = args->end_ns;
data.owner = args->owner;
events.perf_submit(args, &data ,sizeof(data));
return 0;
}
TRACEPOINT_PROBE(btrfs, btrfs_tree_lock)
{
struct data_t data;
bpf_probe_read(data.fsid, FSID_SIZE, args->fsid);
data.start_ns = args->start_ns;
data.end_ns = args->end_ns;
data.owner = args->owner;
events.perf_submit(args, &data ,sizeof(data));
return 0;
}
'''
def usage():
print("%s [-t <time_interval>]" % (argv[0]))
print("<time_interval> can be either in ns, or with unit like \"100ms\"")
exit(1)
def is_fstree(owner):
if owner == 5 or (owner >= 256 and owner <= (2 ** 64 - 256)):
return True
return False
def parse_time_interval(time_str):
try:
if ('s' not in time_str) or ('ns' in time_str):
return int(time_str.split('ns')[0])
if 'us' in time_str:
return int(time_str.split('us')[0]) * 1000
if 'ms' in time_str:
return int(time_str.split('ms')[0]) * 1000 * 1000
if 's' in time_str:
return int(time_str.split('s')[0]) * 1000 * 1000 * 1000
except ValueError as err:
print(str(err))
usage()
# TODO: Don't use such classification while have a good idea to output
# all the needed info
def get_owner_str(owner):
if is_fstree(owner):
return 'SUBVOL'
if owner == 1:
return 'TREE_ROOT'
if owner == 2:
return 'EXTENT_ROOT'
return 'OTHER_ROOTS'
def process_event(cpu, data, size):
event = b["events"].event(data)
global start_time_set
global start_time
global end_time
global time_interval
global results
global fsid
global fsid_set
cur = int(event.start_ns / time_interval) * time_interval
if fsid_set:
current_fsid = uuid.UUID(binascii.hexlify(bytearray(event.fsid)))
if current_fsid != fsid:
return;
if start_time_set:
start_time = min(cur, start_time)
else:
start_time = cur
start_time_set = True
end_time = max(event.end_ns, end_time)
while cur < event.end_ns:
end_ns = min(event.end_ns, cur + time_interval)
start_ns = max(cur, event.start_ns)
if cur not in results:
results[cur] = {}
results[cur]['SUBVOL'] = 0
results[cur]['TREE_ROOT'] = 0
results[cur]['EXTENT_ROOT'] = 0
results[cur]['OTHER_ROOTS'] = 0
results[cur][get_owner_str(event.owner)] += end_ns - start_ns
cur += time_interval
'''
output format (csv):
<aligned timestamp>, <tree root ns>, <extent root ns>, <subvol ns>, <other ns>
'''
def print_results():
print(file=stderr)
if not start_time_set:
print("no data", file=stderr)
exit(0)
cur = start_time
print("%s,%s,%s,%s,%s" % ("timestamp", "root", "extent", "subvol", "other"))
while cur < end_time:
if cur not in results:
print("%d,%d,%d,%d,%d" % (cur - start_time, 0, 0, 0, 0))
else:
print("%d,%d,%d,%d,%d" % (cur - start_time,
results[cur]['SUBVOL'],
results[cur]['TREE_ROOT'],
results[cur]['EXTENT_ROOT'],
results[cur]['OTHER_ROOTS']))
cur += time_interval
# default time interval is 100ms
time_interval = 100 * 1000 * 1000
# @results is a 2 dimension dict.
# [<aligned_timetamp>][<owner_str>] to access, no need to
# worry about non-exist key.
results = defaultdict(dict)
# To catch the first event time stamp
start_time_set = False
start_time = False
fsid_set = False
end_time = 0
try:
opts, args = getopt.getopt(argv[1:], 't:f:')
except getopt.GetoptError as err:
print(str(err), file=stderr)
usage()
if len(args) != 0:
usage()
for opt,arg in opts:
if opt == '-t':
time_interval = int(arg)
if opt == '-f':
fsid = uuid.UUID(arg)
fsid_set = True
b = BPF(text = text_bpf)
b["events"].open_perf_buffer(process_event, page_cnt=64)
print("start recording", file=stderr)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
print_results()
exit(0)