forked from facebookarchive/rbperf
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhandlers.py
More file actions
257 lines (214 loc) · 7.71 KB
/
Copy pathhandlers.py
File metadata and controls
257 lines (214 loc) · 7.71 KB
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import datetime
import queue
import threading
import os
import time
from dataclasses import dataclass
from proto import rbperf_pb2
from storage import StreamingProtobufWriter, CompactProtobufWriter
@dataclass(frozen=True)
class HandlerInfo:
lost: int
total: int
incomplete: int
lost_stacks: int
worked_off: int
filename: str
def __repr__(self):
return (
f"Processed {self.total} events, lost {self.lost} events, lost stacks {self.lost_stacks}, "
f"incomplete: {self.incomplete}, written: {self.worked_off}"
)
class CompactHandler:
def __init__(self, file_object=None):
self.lost = 0
self.total = 0
self.incomplete = 0
self.lost_stacks = 0
self.worked_off = 0
self.stacks = {}
self.queue = queue.Queue()
self.symbol_to_id = {}
self.unique_symbol_count = 0
self.running = True
self.thread = threading.Thread(target=self.worker, daemon=True)
self.thread.start()
if file_object:
self.file = file_object
else:
date = datetime.datetime.now().replace(microsecond=0).isoformat()
self.file = open(f"rbperf-{date}.data", "wb")
self.proto = CompactProtobufWriter(self.file)
hostname, kernel = os.uname()[1:3]
self.profile = rbperf_pb2.Profile(
created_at=int(time.time()), hostname=hostname, kernel_string=kernel,
)
def set_config(self, profile_type):
self.profile.type = profile_type
def process_sample(self, stacktrace):
self.total += 1
self.queue.put(stacktrace)
def worker(self):
while self.running:
try:
stacktrace = self.queue.get(timeout=0.1)
except queue.Empty:
continue
pid = stacktrace["pid"]
comm = stacktrace["comm"]
stack_status = stacktrace["stack_status"]
if stack_status == 1:
self.incomplete += 1
stack = rbperf_pb2.StackTrace(
tid=0,
cpu=0,
comm=comm,
pid=pid,
stack_status=rbperf_pb2.StackTrace.StackStatus.Name(stack_status),
)
stack.interned_frames.extend(
[
rbperf_pb2.InternedFrame(
method=self.get_symbol_id(frame["method_name"]),
path=self.get_symbol_id(frame["path"]),
lineno=frame["lineno"],
)
for frame in stacktrace["frames"]
]
)
self.profile.stacktraces.append(stack)
self.worked_off += 1
self.queue.task_done()
def get_symbol_id(self, symbol):
if symbol in self.symbol_to_id:
# can be a set?
return self.symbol_to_id[symbol]
else:
self.unique_symbol_count += 1
symbol_id = self.unique_symbol_count
self.symbol_to_id[symbol] = symbol_id
return symbol_id
def process_lost_stacks(self, exception):
self.lost_stacks += 1
def process_lost_events(self, count):
self.lost += count
def fill_string_table(self):
for symbol, id_ in self.symbol_to_id.items():
self.profile.string_table[id_] = symbol
def finish(self):
try:
self.running = False
self.thread.join()
self.fill_string_table()
self.proto.write_profile(self.profile)
finally:
self.file.close()
return HandlerInfo(
lost=self.lost,
total=self.total,
incomplete=self.incomplete,
lost_stacks=self.lost_stacks,
worked_off=self.worked_off,
filename=self.file.name,
)
class StreamingHandler:
"""
In cases where we want to profile a very high frequency event or
profile over long periods of time, storing all the samples in memory
and writing everything at once to disk might not be ideal.
This handler writes stacks in a "streaming" fashion: It writes an
incomplete Profile message, which lacks from the stacks or the
symbol table for strings, and instead appends StackTrace frames
to the file, prefixed by their size in bytes. See storage.py for
the implementation.
Note that this format is less efficient storage-wise as we don't
de-duplicate strings.
TODO(javierhonduco): we could maybe compress the file
"""
def __init__(self, file_object=None):
self.lost = 0
self.total = 0
self.incomplete = 0
self.lost_stacks = 0
self.worked_off = 0
self.stacks = {}
self.queue = queue.Queue()
self.running = True
"""
We should process the event as soon as we can so we allow
the next perf event callback to run, otherwise the chances
of losing events increase.
In the other handlers, performance is not a concern as acute
as it's here due to the Protobuf object allocations and
the disk IO during the writes.
"""
self.thread = threading.Thread(target=self.worker, daemon=True)
self.thread.start()
if file_object:
self.file = file_object
else:
date = datetime.datetime.now().replace(microsecond=0).isoformat()
self.file = open(f"rbperf-{date}.data", "wb")
self.proto = StreamingProtobufWriter(self.file)
hostname, kernel = os.uname()[1:3]
self.profile = rbperf_pb2.Profile(
created_at=int(time.time()), hostname=hostname, kernel_string=kernel,
)
def set_config(self, profile_type):
self.profile.type = profile_type
self.proto.write_header(self.profile)
def process_sample(self, stacktrace):
self.total += 1
self.queue.put(stacktrace)
def worker(self):
while self.running:
try:
stacktrace = self.queue.get(timeout=0.1)
except queue.Empty:
continue
stack_status = stacktrace["stack_status"]
if stack_status == 1:
self.incomplete += 1
stack = rbperf_pb2.StackTrace(
timestamp=stacktrace["timestamp"],
tid=0,
cpu=0,
pid=stacktrace["pid"],
comm=stacktrace["comm"],
stack_status=rbperf_pb2.StackTrace.StackStatus.Name(stack_status),
)
stack.frames.extend(
[
rbperf_pb2.Frame(
method=frame["method_name"],
path=frame["path"],
lineno=frame["lineno"],
)
for frame in stacktrace["frames"]
]
)
self.proto.write_stack(stack)
self.worked_off += 1
self.queue.task_done()
def process_lost_stacks(self, exception):
self.lost_stacks += 1
def process_lost_events(self, count):
self.lost += count
def finish(self):
try:
self.running = False
self.thread.join()
finally:
self.file.close()
return HandlerInfo(
lost=self.lost,
total=self.total,
incomplete=self.incomplete,
lost_stacks=self.lost_stacks,
worked_off=self.worked_off,
filename=self.file.name,
)