vernier 0.1.1 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +19 -5
- data/Rakefile +4 -0
- data/examples/threaded_http_requests.rb +38 -0
- data/ext/vernier/extconf.rb +1 -1
- data/ext/vernier/ruby_type_names.h +44 -0
- data/ext/vernier/stack.hh +106 -29
- data/ext/vernier/vernier.cc +1147 -113
- data/lib/vernier/collector.rb +52 -0
- data/lib/vernier/marker.rb +38 -0
- data/lib/vernier/output/firefox.rb +365 -0
- data/lib/vernier/output/top.rb +30 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +167 -5
- data/vernier.gemspec +1 -1
- metadata +10 -5
- data/Gemfile.lock +0 -24
data/ext/vernier/vernier.cc
CHANGED
@@ -1,27 +1,276 @@
|
|
1
1
|
#include <iostream>
|
2
|
+
#include <iomanip>
|
2
3
|
#include <vector>
|
3
4
|
#include <memory>
|
4
5
|
#include <algorithm>
|
5
6
|
#include <sstream>
|
6
7
|
#include <unordered_map>
|
7
8
|
#include <unordered_set>
|
9
|
+
#include <cassert>
|
10
|
+
#include <atomic>
|
11
|
+
#include <mutex>
|
12
|
+
#include <optional>
|
13
|
+
|
14
|
+
#include <sys/time.h>
|
15
|
+
#include <signal.h>
|
16
|
+
#ifdef __APPLE__
|
17
|
+
#include <dispatch/dispatch.h>
|
18
|
+
#else
|
19
|
+
#include <semaphore.h>
|
20
|
+
#endif
|
8
21
|
|
9
22
|
#include "vernier.hh"
|
10
23
|
#include "stack.hh"
|
24
|
+
|
25
|
+
#include "ruby/ruby.h"
|
11
26
|
#include "ruby/debug.h"
|
27
|
+
#include "ruby/thread.h"
|
12
28
|
|
13
|
-
|
29
|
+
// GC event's we'll monitor during profiling
|
30
|
+
#define RUBY_GC_PHASE_EVENTS \
|
31
|
+
RUBY_INTERNAL_EVENT_GC_START | \
|
32
|
+
RUBY_INTERNAL_EVENT_GC_END_MARK | \
|
33
|
+
RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
|
34
|
+
RUBY_INTERNAL_EVENT_GC_ENTER | \
|
35
|
+
RUBY_INTERNAL_EVENT_GC_EXIT
|
36
|
+
|
37
|
+
#define sym(name) ID2SYM(rb_intern_const(name))
|
38
|
+
|
39
|
+
// HACK: This isn't public, but the objspace ext uses it
|
40
|
+
extern "C" size_t rb_obj_memsize_of(VALUE);
|
14
41
|
|
15
|
-
|
42
|
+
using namespace std;
|
16
43
|
|
17
44
|
static VALUE rb_mVernier;
|
45
|
+
static VALUE rb_cVernierResult;
|
46
|
+
static VALUE rb_mVernierMarkerType;
|
47
|
+
static VALUE rb_cVernierCollector;
|
48
|
+
|
49
|
+
class TimeStamp {
|
50
|
+
static const uint64_t nanoseconds_per_second = 1000000000;
|
51
|
+
uint64_t value_ns;
|
52
|
+
|
53
|
+
TimeStamp(uint64_t value_ns) : value_ns(value_ns) {}
|
54
|
+
|
55
|
+
public:
|
56
|
+
TimeStamp() : value_ns(0) {}
|
57
|
+
|
58
|
+
static TimeStamp Now() {
|
59
|
+
struct timespec ts;
|
60
|
+
clock_gettime(CLOCK_MONOTONIC, &ts);
|
61
|
+
return TimeStamp(ts.tv_sec * nanoseconds_per_second + ts.tv_nsec);
|
62
|
+
}
|
63
|
+
|
64
|
+
static TimeStamp Zero() {
|
65
|
+
return TimeStamp(0);
|
66
|
+
}
|
67
|
+
|
68
|
+
static void Sleep(const TimeStamp &time) {
|
69
|
+
struct timespec ts = time.timespec();
|
70
|
+
|
71
|
+
int res;
|
72
|
+
do {
|
73
|
+
res = nanosleep(&ts, &ts);
|
74
|
+
} while (res && errno == EINTR);
|
75
|
+
}
|
76
|
+
|
77
|
+
static TimeStamp from_microseconds(uint64_t us) {
|
78
|
+
return TimeStamp(us * 1000);
|
79
|
+
}
|
80
|
+
|
81
|
+
static TimeStamp from_nanoseconds(uint64_t ns) {
|
82
|
+
return TimeStamp(ns);
|
83
|
+
}
|
84
|
+
|
85
|
+
TimeStamp operator-(const TimeStamp &other) const {
|
86
|
+
TimeStamp result = *this;
|
87
|
+
return result -= other;
|
88
|
+
}
|
89
|
+
|
90
|
+
TimeStamp &operator-=(const TimeStamp &other) {
|
91
|
+
if (value_ns > other.value_ns) {
|
92
|
+
value_ns = value_ns - other.value_ns;
|
93
|
+
} else {
|
94
|
+
// underflow
|
95
|
+
value_ns = 0;
|
96
|
+
}
|
97
|
+
return *this;
|
98
|
+
}
|
99
|
+
|
100
|
+
TimeStamp operator+(const TimeStamp &other) const {
|
101
|
+
TimeStamp result = *this;
|
102
|
+
return result += other;
|
103
|
+
}
|
104
|
+
|
105
|
+
TimeStamp &operator+=(const TimeStamp &other) {
|
106
|
+
uint64_t new_value = value_ns + other.value_ns;
|
107
|
+
value_ns = new_value;
|
108
|
+
return *this;
|
109
|
+
}
|
110
|
+
|
111
|
+
bool operator<(const TimeStamp &other) const {
|
112
|
+
return value_ns < other.value_ns;
|
113
|
+
}
|
114
|
+
|
115
|
+
bool operator<=(const TimeStamp &other) const {
|
116
|
+
return value_ns <= other.value_ns;
|
117
|
+
}
|
118
|
+
|
119
|
+
bool operator>(const TimeStamp &other) const {
|
120
|
+
return value_ns > other.value_ns;
|
121
|
+
}
|
122
|
+
|
123
|
+
bool operator>=(const TimeStamp &other) const {
|
124
|
+
return value_ns >= other.value_ns;
|
125
|
+
}
|
126
|
+
|
127
|
+
uint64_t nanoseconds() const {
|
128
|
+
return value_ns;
|
129
|
+
}
|
130
|
+
|
131
|
+
uint64_t microseconds() const {
|
132
|
+
return value_ns / 1000;
|
133
|
+
}
|
134
|
+
|
135
|
+
bool zero() const {
|
136
|
+
return value_ns == 0;
|
137
|
+
}
|
138
|
+
|
139
|
+
struct timespec timespec() const {
|
140
|
+
struct timespec ts;
|
141
|
+
ts.tv_sec = nanoseconds() / nanoseconds_per_second;
|
142
|
+
ts.tv_nsec = (nanoseconds() % nanoseconds_per_second);
|
143
|
+
return ts;
|
144
|
+
}
|
145
|
+
};
|
146
|
+
|
147
|
+
std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
|
148
|
+
os << info.nanoseconds() << "ns";
|
149
|
+
return os;
|
150
|
+
}
|
18
151
|
|
19
|
-
|
20
|
-
|
21
|
-
|
152
|
+
// A basic semaphore built on sem_wait/sem_post
|
153
|
+
// post() is guaranteed to be async-signal-safe
|
154
|
+
class SamplerSemaphore {
|
155
|
+
#ifdef __APPLE__
|
156
|
+
dispatch_semaphore_t sem;
|
157
|
+
#else
|
158
|
+
sem_t sem;
|
159
|
+
#endif
|
22
160
|
|
23
|
-
|
24
|
-
|
161
|
+
public:
|
162
|
+
|
163
|
+
SamplerSemaphore(unsigned int value = 0) {
|
164
|
+
#ifdef __APPLE__
|
165
|
+
sem = dispatch_semaphore_create(value);
|
166
|
+
#else
|
167
|
+
sem_init(&sem, 0, value);
|
168
|
+
#endif
|
169
|
+
};
|
170
|
+
|
171
|
+
~SamplerSemaphore() {
|
172
|
+
#ifdef __APPLE__
|
173
|
+
dispatch_release(sem);
|
174
|
+
#else
|
175
|
+
sem_destroy(&sem);
|
176
|
+
#endif
|
177
|
+
};
|
178
|
+
|
179
|
+
void wait() {
|
180
|
+
#ifdef __APPLE__
|
181
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
182
|
+
#else
|
183
|
+
int ret;
|
184
|
+
do {
|
185
|
+
ret = sem_wait(&sem);
|
186
|
+
} while (ret && errno == EINTR);
|
187
|
+
assert(ret == 0);
|
188
|
+
#endif
|
189
|
+
}
|
190
|
+
|
191
|
+
void post() {
|
192
|
+
#ifdef __APPLE__
|
193
|
+
dispatch_semaphore_signal(sem);
|
194
|
+
#else
|
195
|
+
sem_post(&sem);
|
196
|
+
#endif
|
197
|
+
}
|
198
|
+
};
|
199
|
+
|
200
|
+
struct RawSample {
|
201
|
+
constexpr static int MAX_LEN = 2048;
|
202
|
+
VALUE frames[MAX_LEN];
|
203
|
+
int lines[MAX_LEN];
|
204
|
+
int len;
|
205
|
+
bool gc;
|
206
|
+
|
207
|
+
RawSample() : len(0), gc(false) { }
|
208
|
+
|
209
|
+
int size() const {
|
210
|
+
return len;
|
211
|
+
}
|
212
|
+
|
213
|
+
Frame frame(int i) const {
|
214
|
+
const Frame frame = {frames[i], lines[i]};
|
215
|
+
return frame;
|
216
|
+
}
|
217
|
+
|
218
|
+
void sample() {
|
219
|
+
if (!ruby_native_thread_p()) {
|
220
|
+
clear();
|
221
|
+
return;
|
222
|
+
}
|
223
|
+
|
224
|
+
if (rb_during_gc()) {
|
225
|
+
gc = true;
|
226
|
+
len = 0;
|
227
|
+
} else {
|
228
|
+
gc = false;
|
229
|
+
len = rb_profile_frames(0, MAX_LEN, frames, lines);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
void clear() {
|
234
|
+
len = 0;
|
235
|
+
gc = false;
|
236
|
+
}
|
237
|
+
|
238
|
+
bool empty() const {
|
239
|
+
return len == 0;
|
240
|
+
}
|
241
|
+
};
|
242
|
+
|
243
|
+
// Based very loosely on the design of Gecko's SigHandlerCoordinator
|
244
|
+
// This is used for communication between the profiler thread and the signal
|
245
|
+
// handlers in the observed thread.
|
246
|
+
struct LiveSample {
|
247
|
+
RawSample sample;
|
248
|
+
|
249
|
+
SamplerSemaphore sem_complete;
|
250
|
+
|
251
|
+
// Wait for a sample to be collected by the signal handler on another thread
|
252
|
+
void wait() {
|
253
|
+
sem_complete.wait();
|
254
|
+
}
|
255
|
+
|
256
|
+
int size() const {
|
257
|
+
return sample.size();
|
258
|
+
}
|
259
|
+
|
260
|
+
Frame frame(int i) const {
|
261
|
+
return sample.frame(i);
|
262
|
+
}
|
263
|
+
|
264
|
+
// Called from a signal handler in the observed thread in order to take a
|
265
|
+
// sample and signal to the proifiler thread that the sample is ready.
|
266
|
+
//
|
267
|
+
// CRuby doesn't guarantee that rb_profile_frames can be used as
|
268
|
+
// async-signal-safe but in practice it seems to be.
|
269
|
+
// sem_post is safe in an async-signal-safe context.
|
270
|
+
void sample_current_thread() {
|
271
|
+
sample.sample();
|
272
|
+
sem_complete.post();
|
273
|
+
}
|
25
274
|
};
|
26
275
|
|
27
276
|
struct TraceArg {
|
@@ -42,149 +291,934 @@ struct TraceArg {
|
|
42
291
|
}
|
43
292
|
};
|
44
293
|
|
45
|
-
|
294
|
+
struct FrameList {
|
295
|
+
std::unordered_map<std::string, int> string_to_idx;
|
296
|
+
std::vector<std::string> string_list;
|
46
297
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
TraceArg tp(tpval);
|
53
|
-
collector->allocated_objects++;
|
298
|
+
int string_index(const std::string str) {
|
299
|
+
auto it = string_to_idx.find(str);
|
300
|
+
if (it == string_to_idx.end()) {
|
301
|
+
int idx = string_list.size();
|
302
|
+
string_list.push_back(str);
|
54
303
|
|
55
|
-
|
56
|
-
|
57
|
-
|
304
|
+
auto result = string_to_idx.insert({str, idx});
|
305
|
+
it = result.first;
|
306
|
+
}
|
58
307
|
|
59
|
-
|
60
|
-
collector->unique_frames.insert(frames_buffer[i]);
|
308
|
+
return it->second;
|
61
309
|
}
|
62
310
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
311
|
+
struct FrameWithInfo {
|
312
|
+
Frame frame;
|
313
|
+
FrameInfo info;
|
314
|
+
};
|
315
|
+
|
316
|
+
std::unordered_map<Frame, int> frame_to_idx;
|
317
|
+
std::vector<Frame> frame_list;
|
318
|
+
std::vector<FrameWithInfo> frame_with_info_list;
|
319
|
+
int frame_index(const Frame frame) {
|
320
|
+
auto it = frame_to_idx.find(frame);
|
321
|
+
if (it == frame_to_idx.end()) {
|
322
|
+
int idx = frame_list.size();
|
323
|
+
frame_list.push_back(frame);
|
324
|
+
auto result = frame_to_idx.insert({frame, idx});
|
325
|
+
it = result.first;
|
326
|
+
}
|
327
|
+
return it->second;
|
328
|
+
}
|
329
|
+
|
330
|
+
struct StackNode {
|
331
|
+
std::unordered_map<Frame, int> children;
|
332
|
+
Frame frame;
|
333
|
+
int parent;
|
334
|
+
int index;
|
335
|
+
|
336
|
+
StackNode(Frame frame, int index, int parent) : frame(frame), index(index), parent(parent) {}
|
337
|
+
|
338
|
+
// root
|
339
|
+
StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
|
340
|
+
};
|
341
|
+
|
342
|
+
StackNode root_stack_node;
|
343
|
+
vector<StackNode> stack_node_list;
|
344
|
+
|
345
|
+
int stack_index(const RawSample &stack) {
|
346
|
+
if (stack.empty()) {
|
347
|
+
throw std::runtime_error("empty stack");
|
348
|
+
}
|
349
|
+
|
350
|
+
StackNode *node = &root_stack_node;
|
351
|
+
for (int i = stack.size() - 1; i >= 0; i--) {
|
352
|
+
const Frame &frame = stack.frame(i);
|
353
|
+
node = next_stack_node(node, frame);
|
354
|
+
}
|
355
|
+
return node->index;
|
356
|
+
}
|
357
|
+
|
358
|
+
StackNode *next_stack_node(StackNode *node, const Frame &frame) {
|
359
|
+
int next_node_idx = node->children[frame];
|
360
|
+
if (next_node_idx == 0) {
|
361
|
+
// insert a new node
|
362
|
+
next_node_idx = stack_node_list.size();
|
363
|
+
node->children[frame] = next_node_idx;
|
364
|
+
stack_node_list.emplace_back(
|
365
|
+
frame,
|
366
|
+
next_node_idx,
|
367
|
+
node->index
|
368
|
+
);
|
369
|
+
}
|
370
|
+
|
371
|
+
return &stack_node_list[next_node_idx];
|
372
|
+
}
|
373
|
+
|
374
|
+
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
375
|
+
// which allocates.
|
376
|
+
void finalize() {
|
377
|
+
for (const auto &stack_node : stack_node_list) {
|
378
|
+
frame_index(stack_node.frame);
|
379
|
+
}
|
380
|
+
for (const auto &frame : frame_list) {
|
381
|
+
frame_with_info_list.push_back(FrameWithInfo{frame, frame.info()});
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
void mark_frames() {
|
386
|
+
for (auto stack_node: stack_node_list) {
|
387
|
+
rb_gc_mark(stack_node.frame.frame);
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
void clear() {
|
392
|
+
string_list.clear();
|
393
|
+
frame_list.clear();
|
394
|
+
stack_node_list.clear();
|
395
|
+
frame_with_info_list.clear();
|
396
|
+
|
397
|
+
string_to_idx.clear();
|
398
|
+
frame_to_idx.clear();
|
399
|
+
root_stack_node.children.clear();
|
400
|
+
}
|
401
|
+
|
402
|
+
void write_result(VALUE result) {
|
403
|
+
FrameList &frame_list = *this;
|
404
|
+
|
405
|
+
VALUE stack_table = rb_hash_new();
|
406
|
+
rb_ivar_set(result, rb_intern("@stack_table"), stack_table);
|
407
|
+
VALUE stack_table_parent = rb_ary_new();
|
408
|
+
VALUE stack_table_frame = rb_ary_new();
|
409
|
+
rb_hash_aset(stack_table, sym("parent"), stack_table_parent);
|
410
|
+
rb_hash_aset(stack_table, sym("frame"), stack_table_frame);
|
411
|
+
for (const auto &stack : frame_list.stack_node_list) {
|
412
|
+
VALUE parent_val = stack.parent == -1 ? Qnil : INT2NUM(stack.parent);
|
413
|
+
rb_ary_push(stack_table_parent, parent_val);
|
414
|
+
rb_ary_push(stack_table_frame, INT2NUM(frame_list.frame_index(stack.frame)));
|
415
|
+
}
|
416
|
+
|
417
|
+
VALUE frame_table = rb_hash_new();
|
418
|
+
rb_ivar_set(result, rb_intern("@frame_table"), frame_table);
|
419
|
+
VALUE frame_table_func = rb_ary_new();
|
420
|
+
VALUE frame_table_line = rb_ary_new();
|
421
|
+
rb_hash_aset(frame_table, sym("func"), frame_table_func);
|
422
|
+
rb_hash_aset(frame_table, sym("line"), frame_table_line);
|
423
|
+
//for (const auto &frame : frame_list.frame_list) {
|
424
|
+
for (int i = 0; i < frame_list.frame_with_info_list.size(); i++) {
|
425
|
+
const auto &frame = frame_list.frame_with_info_list[i];
|
426
|
+
rb_ary_push(frame_table_func, INT2NUM(i));
|
427
|
+
rb_ary_push(frame_table_line, INT2NUM(frame.frame.line));
|
428
|
+
}
|
429
|
+
|
430
|
+
// TODO: dedup funcs before this step
|
431
|
+
VALUE func_table = rb_hash_new();
|
432
|
+
rb_ivar_set(result, rb_intern("@func_table"), func_table);
|
433
|
+
VALUE func_table_name = rb_ary_new();
|
434
|
+
VALUE func_table_filename = rb_ary_new();
|
435
|
+
VALUE func_table_first_line = rb_ary_new();
|
436
|
+
rb_hash_aset(func_table, sym("name"), func_table_name);
|
437
|
+
rb_hash_aset(func_table, sym("filename"), func_table_filename);
|
438
|
+
rb_hash_aset(func_table, sym("first_line"), func_table_first_line);
|
439
|
+
for (const auto &frame : frame_list.frame_with_info_list) {
|
440
|
+
const std::string label = frame.info.label;
|
441
|
+
const std::string filename = frame.info.file;
|
442
|
+
const int first_line = frame.info.first_lineno;
|
443
|
+
|
444
|
+
rb_ary_push(func_table_name, rb_str_new(label.c_str(), label.length()));
|
445
|
+
rb_ary_push(func_table_filename, rb_str_new(filename.c_str(), filename.length()));
|
446
|
+
rb_ary_push(func_table_first_line, INT2NUM(first_line));
|
447
|
+
}
|
448
|
+
}
|
449
|
+
};
|
450
|
+
|
451
|
+
class BaseCollector {
|
452
|
+
protected:
|
453
|
+
|
454
|
+
virtual void reset() {
|
455
|
+
frame_list.clear();
|
456
|
+
}
|
457
|
+
|
458
|
+
public:
|
459
|
+
bool running = false;
|
460
|
+
FrameList frame_list;
|
461
|
+
|
462
|
+
virtual bool start() {
|
463
|
+
if (running) {
|
464
|
+
return false;
|
465
|
+
} else {
|
466
|
+
running = true;
|
467
|
+
return true;
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
471
|
+
virtual VALUE stop() {
|
472
|
+
if (!running) {
|
473
|
+
rb_raise(rb_eRuntimeError, "collector not running");
|
474
|
+
}
|
475
|
+
running = false;
|
476
|
+
|
477
|
+
return Qnil;
|
478
|
+
}
|
479
|
+
|
480
|
+
virtual void sample() {
|
481
|
+
rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
|
482
|
+
};
|
483
|
+
|
484
|
+
virtual void mark() {
|
485
|
+
frame_list.mark_frames();
|
486
|
+
};
|
487
|
+
|
488
|
+
virtual VALUE get_markers() {
|
489
|
+
return rb_ary_new();
|
490
|
+
};
|
491
|
+
};
|
492
|
+
|
493
|
+
class CustomCollector : public BaseCollector {
|
494
|
+
std::vector<int> samples;
|
495
|
+
|
496
|
+
void sample() {
|
497
|
+
RawSample sample;
|
498
|
+
sample.sample();
|
499
|
+
int stack_index = frame_list.stack_index(sample);
|
500
|
+
|
501
|
+
samples.push_back(stack_index);
|
502
|
+
}
|
503
|
+
|
504
|
+
VALUE stop() {
|
505
|
+
BaseCollector::stop();
|
506
|
+
|
507
|
+
frame_list.finalize();
|
508
|
+
|
509
|
+
VALUE result = build_collector_result();
|
510
|
+
|
511
|
+
reset();
|
512
|
+
|
513
|
+
return result;
|
514
|
+
}
|
515
|
+
|
516
|
+
VALUE build_collector_result() {
|
517
|
+
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
518
|
+
|
519
|
+
VALUE samples = rb_ary_new();
|
520
|
+
rb_ivar_set(result, rb_intern("@samples"), samples);
|
521
|
+
VALUE weights = rb_ary_new();
|
522
|
+
rb_ivar_set(result, rb_intern("@weights"), weights);
|
523
|
+
|
524
|
+
for (auto& stack_index: this->samples) {
|
525
|
+
rb_ary_push(samples, INT2NUM(stack_index));
|
526
|
+
rb_ary_push(weights, INT2NUM(1));
|
527
|
+
}
|
528
|
+
|
529
|
+
frame_list.write_result(result);
|
530
|
+
|
531
|
+
return result;
|
532
|
+
}
|
533
|
+
};
|
534
|
+
|
535
|
+
class RetainedCollector : public BaseCollector {
|
536
|
+
void reset() {
|
537
|
+
object_frames.clear();
|
538
|
+
object_list.clear();
|
539
|
+
|
540
|
+
BaseCollector::reset();
|
541
|
+
}
|
542
|
+
|
543
|
+
void record(VALUE obj) {
|
544
|
+
RawSample sample;
|
545
|
+
sample.sample();
|
546
|
+
int stack_index = frame_list.stack_index(sample);
|
547
|
+
|
548
|
+
object_list.push_back(obj);
|
549
|
+
object_frames.emplace(obj, stack_index);
|
550
|
+
}
|
551
|
+
|
552
|
+
std::unordered_map<VALUE, int> object_frames;
|
553
|
+
std::vector<VALUE> object_list;
|
554
|
+
|
555
|
+
VALUE tp_newobj = Qnil;
|
556
|
+
VALUE tp_freeobj = Qnil;
|
557
|
+
|
558
|
+
static void newobj_i(VALUE tpval, void *data) {
|
559
|
+
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
560
|
+
TraceArg tp(tpval);
|
561
|
+
|
562
|
+
collector->record(tp.obj);
|
563
|
+
}
|
564
|
+
|
565
|
+
static void freeobj_i(VALUE tpval, void *data) {
|
566
|
+
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
567
|
+
TraceArg tp(tpval);
|
568
|
+
|
569
|
+
collector->object_frames.erase(tp.obj);
|
570
|
+
}
|
571
|
+
|
572
|
+
public:
|
573
|
+
|
574
|
+
bool start() {
|
575
|
+
if (!BaseCollector::start()) {
|
576
|
+
return false;
|
577
|
+
}
|
578
|
+
|
579
|
+
tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
|
580
|
+
tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, this);
|
581
|
+
|
582
|
+
rb_tracepoint_enable(tp_newobj);
|
583
|
+
rb_tracepoint_enable(tp_freeobj);
|
584
|
+
|
585
|
+
return true;
|
586
|
+
}
|
587
|
+
|
588
|
+
VALUE stop() {
|
589
|
+
BaseCollector::stop();
|
590
|
+
|
591
|
+
// GC before we start turning stacks into strings
|
592
|
+
rb_gc();
|
593
|
+
|
594
|
+
// Stop tracking any more new objects, but we'll continue tracking free'd
|
595
|
+
// objects as we may be able to free some as we remove our own references
|
596
|
+
// to stack frames.
|
597
|
+
rb_tracepoint_disable(tp_newobj);
|
598
|
+
tp_newobj = Qnil;
|
599
|
+
|
600
|
+
frame_list.finalize();
|
601
|
+
|
602
|
+
// We should have collected info for all our frames, so no need to continue
|
603
|
+
// marking them
|
604
|
+
// FIXME: previously here we cleared the list of frames so we would stop
|
605
|
+
// marking them. Maybe now we should set a flag so that we stop marking them
|
606
|
+
|
607
|
+
// GC again
|
608
|
+
rb_gc();
|
609
|
+
|
610
|
+
rb_tracepoint_disable(tp_freeobj);
|
611
|
+
tp_freeobj = Qnil;
|
612
|
+
|
613
|
+
VALUE result = build_collector_result();
|
614
|
+
|
615
|
+
reset();
|
616
|
+
|
617
|
+
return result;
|
618
|
+
}
|
619
|
+
|
620
|
+
VALUE build_collector_result() {
|
621
|
+
RetainedCollector *collector = this;
|
622
|
+
FrameList &frame_list = collector->frame_list;
|
623
|
+
|
624
|
+
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
625
|
+
|
626
|
+
VALUE samples = rb_ary_new();
|
627
|
+
rb_ivar_set(result, rb_intern("@samples"), samples);
|
628
|
+
VALUE weights = rb_ary_new();
|
629
|
+
rb_ivar_set(result, rb_intern("@weights"), weights);
|
630
|
+
|
631
|
+
for (auto& obj: collector->object_list) {
|
632
|
+
const auto search = collector->object_frames.find(obj);
|
633
|
+
if (search != collector->object_frames.end()) {
|
634
|
+
int stack_index = search->second;
|
635
|
+
|
636
|
+
rb_ary_push(samples, INT2NUM(stack_index));
|
637
|
+
rb_ary_push(weights, INT2NUM(rb_obj_memsize_of(obj)));
|
638
|
+
}
|
639
|
+
}
|
640
|
+
|
641
|
+
frame_list.write_result(result);
|
642
|
+
|
643
|
+
return result;
|
644
|
+
}
|
645
|
+
|
646
|
+
void mark() {
|
647
|
+
// We don't mark the objects, but we MUST mark the frames, otherwise they
|
648
|
+
// can be garbage collected.
|
649
|
+
// When we stop collection we will stringify the remaining frames, and then
|
650
|
+
// clear them from the set, allowing them to be removed from out output.
|
651
|
+
frame_list.mark_frames();
|
652
|
+
|
653
|
+
rb_gc_mark(tp_newobj);
|
654
|
+
rb_gc_mark(tp_freeobj);
|
655
|
+
}
|
656
|
+
};
|
657
|
+
|
658
|
+
typedef uint64_t native_thread_id_t;
|
659
|
+
|
660
|
+
class Thread {
|
661
|
+
public:
|
662
|
+
static native_thread_id_t get_native_thread_id() {
|
663
|
+
#ifdef __APPLE__
|
664
|
+
uint64_t thread_id;
|
665
|
+
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
666
|
+
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
667
|
+
return thread_id;
|
668
|
+
#else
|
669
|
+
return gettid();
|
670
|
+
#endif
|
671
|
+
}
|
672
|
+
|
673
|
+
enum State {
|
674
|
+
STARTED,
|
675
|
+
RUNNING,
|
676
|
+
SUSPENDED,
|
677
|
+
STOPPED
|
678
|
+
};
|
679
|
+
|
680
|
+
pthread_t pthread_id;
|
681
|
+
native_thread_id_t native_tid;
|
682
|
+
State state;
|
683
|
+
|
684
|
+
TimeStamp state_changed_at;
|
685
|
+
TimeStamp started_at;
|
686
|
+
TimeStamp stopped_at;
|
687
|
+
|
688
|
+
RawSample stack_on_suspend;
|
689
|
+
|
690
|
+
std::string name;
|
691
|
+
|
692
|
+
Thread(State state) : state(state) {
|
693
|
+
pthread_id = pthread_self();
|
694
|
+
native_tid = get_native_thread_id();
|
695
|
+
started_at = state_changed_at = TimeStamp::Now();
|
696
|
+
}
|
697
|
+
|
698
|
+
void set_state(State new_state) {
|
699
|
+
if (state == Thread::State::STOPPED) {
|
700
|
+
return;
|
701
|
+
}
|
702
|
+
|
703
|
+
auto now = TimeStamp::Now();
|
704
|
+
|
705
|
+
state = new_state;
|
706
|
+
state_changed_at = now;
|
707
|
+
if (new_state == State::STARTED) {
|
708
|
+
if (started_at.zero()) {
|
709
|
+
started_at = now;
|
710
|
+
}
|
711
|
+
} else if (new_state == State::STOPPED) {
|
712
|
+
stopped_at = now;
|
713
|
+
|
714
|
+
capture_name();
|
715
|
+
}
|
716
|
+
}
|
717
|
+
|
718
|
+
bool running() {
|
719
|
+
return state != State::STOPPED;
|
720
|
+
}
|
721
|
+
|
722
|
+
void capture_name() {
|
723
|
+
char buf[128];
|
724
|
+
int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
725
|
+
if (rc == 0)
|
726
|
+
name = std::string(buf);
|
727
|
+
}
|
728
|
+
};
|
729
|
+
|
730
|
+
class Marker {
|
731
|
+
public:
|
732
|
+
enum Type {
|
733
|
+
MARKER_GVL_THREAD_STARTED,
|
734
|
+
MARKER_GVL_THREAD_READY,
|
735
|
+
MARKER_GVL_THREAD_RESUMED,
|
736
|
+
MARKER_GVL_THREAD_SUSPENDED,
|
737
|
+
MARKER_GVL_THREAD_EXITED,
|
738
|
+
|
739
|
+
MARKER_GC_START,
|
740
|
+
MARKER_GC_END_MARK,
|
741
|
+
MARKER_GC_END_SWEEP,
|
742
|
+
MARKER_GC_ENTER,
|
743
|
+
MARKER_GC_EXIT,
|
744
|
+
|
745
|
+
MARKER_MAX,
|
746
|
+
};
|
747
|
+
Type type;
|
748
|
+
TimeStamp timestamp;
|
749
|
+
native_thread_id_t thread_id;
|
750
|
+
};
|
751
|
+
|
752
|
+
class MarkerTable {
|
753
|
+
public:
|
754
|
+
std::vector<Marker> list;
|
755
|
+
std::mutex mutex;
|
756
|
+
|
757
|
+
void record(Marker::Type type) {
|
758
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
759
|
+
|
760
|
+
list.push_back({ type, TimeStamp::Now(), Thread::get_native_thread_id() });
|
761
|
+
}
|
762
|
+
};
|
763
|
+
|
764
|
+
extern "C" int ruby_thread_has_gvl_p(void);
|
765
|
+
|
766
|
+
class ThreadTable {
|
767
|
+
public:
|
768
|
+
std::vector<Thread> list;
|
769
|
+
std::mutex mutex;
|
770
|
+
|
771
|
+
void started() {
|
772
|
+
//const std::lock_guard<std::mutex> lock(mutex);
|
773
|
+
|
774
|
+
//list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
|
775
|
+
set_state(Thread::State::STARTED);
|
776
|
+
}
|
777
|
+
|
778
|
+
void set_state(Thread::State new_state) {
|
779
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
780
|
+
|
781
|
+
pthread_t current_thread = pthread_self();
|
782
|
+
//cerr << "set state=" << new_state << " thread=" << gettid() << endl;
|
783
|
+
|
784
|
+
for (auto &thread : list) {
|
785
|
+
if (pthread_equal(current_thread, thread.pthread_id)) {
|
786
|
+
thread.set_state(new_state);
|
787
|
+
|
788
|
+
if (new_state == Thread::State::SUSPENDED) {
|
789
|
+
thread.stack_on_suspend.sample();
|
790
|
+
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
791
|
+
}
|
792
|
+
return;
|
793
|
+
}
|
794
|
+
}
|
795
|
+
|
796
|
+
pid_t native_tid = Thread::get_native_thread_id();
|
797
|
+
list.emplace_back(new_state);
|
798
|
+
}
|
799
|
+
};
|
800
|
+
|
801
|
+
enum Category{
|
802
|
+
CATEGORY_NORMAL,
|
803
|
+
CATEGORY_IDLE
|
804
|
+
};
|
805
|
+
|
806
|
+
class TimeCollector : public BaseCollector {
|
807
|
+
std::vector<int> samples;
|
808
|
+
std::vector<TimeStamp> timestamps;
|
809
|
+
std::vector<native_thread_id_t> sample_threads;
|
810
|
+
std::vector<Category> sample_categories;
|
811
|
+
|
812
|
+
MarkerTable markers;
|
813
|
+
ThreadTable threads;
|
814
|
+
|
815
|
+
pthread_t sample_thread;
|
816
|
+
|
817
|
+
atomic_bool running;
|
818
|
+
SamplerSemaphore thread_stopped;
|
819
|
+
|
820
|
+
static inline LiveSample *live_sample;
|
821
|
+
|
822
|
+
TimeStamp started_at;
|
823
|
+
TimeStamp interval;
|
824
|
+
|
825
|
+
public:
|
826
|
+
TimeCollector(TimeStamp interval) : interval(interval) {
|
827
|
+
}
|
828
|
+
|
829
|
+
private:
|
830
|
+
|
831
|
+
void record_sample(const RawSample &sample, TimeStamp time, const Thread &thread, Category category) {
|
832
|
+
if (!sample.empty()) {
|
833
|
+
int stack_index = frame_list.stack_index(sample);
|
834
|
+
samples.push_back(stack_index);
|
835
|
+
timestamps.push_back(time);
|
836
|
+
sample_threads.push_back(thread.native_tid);
|
837
|
+
sample_categories.push_back(category);
|
838
|
+
}
|
839
|
+
}
|
840
|
+
|
841
|
+
static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
|
842
|
+
assert(live_sample);
|
843
|
+
live_sample->sample_current_thread();
|
844
|
+
}
|
845
|
+
|
846
|
+
VALUE get_markers() {
|
847
|
+
VALUE list = rb_ary_new();
|
848
|
+
|
849
|
+
for (auto& marker: this->markers.list) {
|
850
|
+
VALUE record[3] = {0};
|
851
|
+
record[0] = ULL2NUM(marker.thread_id);
|
852
|
+
record[1] = INT2NUM(marker.type);
|
853
|
+
record[2] = ULL2NUM(marker.timestamp.nanoseconds());
|
854
|
+
rb_ary_push(list, rb_ary_new_from_values(3, record));
|
855
|
+
}
|
856
|
+
|
857
|
+
return list;
|
858
|
+
}
|
859
|
+
|
860
|
+
void sample_thread_run() {
|
861
|
+
LiveSample sample;
|
862
|
+
live_sample = &sample;
|
863
|
+
|
864
|
+
TimeStamp next_sample_schedule = TimeStamp::Now();
|
865
|
+
while (running) {
|
866
|
+
TimeStamp sample_start = TimeStamp::Now();
|
867
|
+
|
868
|
+
threads.mutex.lock();
|
869
|
+
for (auto thread : threads.list) {
|
870
|
+
//if (thread.state == Thread::State::RUNNING) {
|
871
|
+
if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend.size() == 0)) {
|
872
|
+
if (pthread_kill(thread.pthread_id, SIGPROF)) {
|
873
|
+
rb_bug("pthread_kill failed");
|
874
|
+
}
|
875
|
+
sample.wait();
|
876
|
+
|
877
|
+
if (sample.sample.gc) {
|
878
|
+
// fprintf(stderr, "skipping GC sample\n");
|
879
|
+
} else {
|
880
|
+
record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
|
881
|
+
}
|
882
|
+
} else if (thread.state == Thread::State::SUSPENDED) {
|
883
|
+
record_sample(thread.stack_on_suspend, sample_start, thread, CATEGORY_IDLE);
|
884
|
+
} else {
|
885
|
+
}
|
886
|
+
}
|
887
|
+
threads.mutex.unlock();
|
888
|
+
|
889
|
+
TimeStamp sample_complete = TimeStamp::Now();
|
890
|
+
|
891
|
+
next_sample_schedule += interval;
|
892
|
+
|
893
|
+
if (next_sample_schedule < sample_complete) {
|
894
|
+
//fprintf(stderr, "fell behind by %ius\n", (sample_complete - next_sample_schedule).microseconds());
|
895
|
+
next_sample_schedule = sample_complete + interval;
|
896
|
+
}
|
897
|
+
|
898
|
+
TimeStamp sleep_time = next_sample_schedule - sample_complete;
|
899
|
+
TimeStamp::Sleep(sleep_time);
|
900
|
+
}
|
901
|
+
|
902
|
+
live_sample = NULL;
|
903
|
+
|
904
|
+
thread_stopped.post();
|
905
|
+
}
|
906
|
+
|
907
|
+
static void *sample_thread_entry(void *arg) {
|
908
|
+
TimeCollector *collector = static_cast<TimeCollector *>(arg);
|
909
|
+
collector->sample_thread_run();
|
910
|
+
return NULL;
|
911
|
+
}
|
912
|
+
|
913
|
+
static void internal_gc_event_cb(VALUE tpval, void *data) {
|
914
|
+
TimeCollector *collector = static_cast<TimeCollector *>(data);
|
915
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
916
|
+
int event = rb_tracearg_event_flag(tparg);
|
917
|
+
|
918
|
+
switch (event) {
|
919
|
+
case RUBY_INTERNAL_EVENT_GC_START:
|
920
|
+
collector->markers.record(Marker::Type::MARKER_GC_START);
|
921
|
+
break;
|
922
|
+
case RUBY_INTERNAL_EVENT_GC_END_MARK:
|
923
|
+
collector->markers.record(Marker::Type::MARKER_GC_END_MARK);
|
924
|
+
break;
|
925
|
+
case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
|
926
|
+
collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
|
927
|
+
break;
|
928
|
+
case RUBY_INTERNAL_EVENT_GC_ENTER:
|
929
|
+
collector->markers.record(Marker::Type::MARKER_GC_ENTER);
|
930
|
+
break;
|
931
|
+
case RUBY_INTERNAL_EVENT_GC_EXIT:
|
932
|
+
collector->markers.record(Marker::Type::MARKER_GC_EXIT);
|
933
|
+
break;
|
934
|
+
}
|
935
|
+
}
|
936
|
+
|
937
|
+
static void internal_thread_event_cb(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *data) {
|
938
|
+
TimeCollector *collector = static_cast<TimeCollector *>(data);
|
939
|
+
//cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
|
940
|
+
|
941
|
+
switch (event) {
|
942
|
+
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
943
|
+
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
944
|
+
collector->threads.started();
|
945
|
+
break;
|
946
|
+
case RUBY_INTERNAL_THREAD_EVENT_READY:
|
947
|
+
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_READY);
|
948
|
+
break;
|
949
|
+
case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
|
950
|
+
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_RESUMED);
|
951
|
+
collector->threads.set_state(Thread::State::RUNNING);
|
952
|
+
break;
|
953
|
+
case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
|
954
|
+
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_SUSPENDED);
|
955
|
+
collector->threads.set_state(Thread::State::SUSPENDED);
|
956
|
+
break;
|
957
|
+
case RUBY_INTERNAL_THREAD_EVENT_EXITED:
|
958
|
+
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
959
|
+
collector->threads.set_state(Thread::State::STOPPED);
|
960
|
+
break;
|
961
|
+
|
962
|
+
}
|
963
|
+
}
|
964
|
+
|
965
|
+
rb_internal_thread_event_hook_t *thread_hook;
|
966
|
+
VALUE gc_hook;
|
967
|
+
|
968
|
+
bool start() {
|
969
|
+
if (!BaseCollector::start()) {
|
970
|
+
return false;
|
971
|
+
}
|
972
|
+
|
973
|
+
started_at = TimeStamp::Now();
|
974
|
+
|
975
|
+
struct sigaction sa;
|
976
|
+
sa.sa_sigaction = signal_handler;
|
977
|
+
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
978
|
+
sigemptyset(&sa.sa_mask);
|
979
|
+
sigaction(SIGPROF, &sa, NULL);
|
980
|
+
|
981
|
+
running = true;
|
982
|
+
|
983
|
+
int ret = pthread_create(&sample_thread, NULL, &sample_thread_entry, this);
|
984
|
+
if (ret != 0) {
|
985
|
+
perror("pthread_create");
|
986
|
+
rb_bug("pthread_create");
|
987
|
+
}
|
988
|
+
|
989
|
+
// Set the state of the current Ruby thread to RUNNING.
|
990
|
+
// We want to have at least one thread in our thread list because it's
|
991
|
+
// possible that the profile might be such that we don't get any
|
992
|
+
// thread switch events and we need at least one
|
993
|
+
this->threads.set_state(Thread::State::RUNNING);
|
994
|
+
|
995
|
+
thread_hook = rb_internal_thread_add_event_hook(internal_thread_event_cb, RUBY_INTERNAL_THREAD_EVENT_MASK, this);
|
996
|
+
gc_hook = rb_tracepoint_new(0, RUBY_GC_PHASE_EVENTS, internal_gc_event_cb, (void *)this);
|
997
|
+
rb_tracepoint_enable(gc_hook);
|
998
|
+
|
999
|
+
return true;
|
1000
|
+
}
|
1001
|
+
|
1002
|
+
VALUE stop() {
|
1003
|
+
BaseCollector::stop();
|
1004
|
+
|
1005
|
+
running = false;
|
1006
|
+
thread_stopped.wait();
|
1007
|
+
|
1008
|
+
struct sigaction sa;
|
1009
|
+
sa.sa_handler = SIG_IGN;
|
1010
|
+
sa.sa_flags = SA_RESTART;
|
1011
|
+
sigemptyset(&sa.sa_mask);
|
1012
|
+
sigaction(SIGPROF, &sa, NULL);
|
1013
|
+
|
1014
|
+
rb_internal_thread_remove_event_hook(thread_hook);
|
1015
|
+
rb_tracepoint_disable(gc_hook);
|
1016
|
+
|
1017
|
+
// capture thread names
|
1018
|
+
for (auto& thread: this->threads.list) {
|
1019
|
+
if (thread.running()) {
|
1020
|
+
thread.capture_name();
|
1021
|
+
}
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
frame_list.finalize();
|
1025
|
+
|
1026
|
+
VALUE result = build_collector_result();
|
1027
|
+
|
1028
|
+
reset();
|
1029
|
+
|
1030
|
+
return result;
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
VALUE build_collector_result() {
|
1034
|
+
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
1035
|
+
|
1036
|
+
VALUE meta = rb_hash_new();
|
1037
|
+
rb_ivar_set(result, rb_intern("@meta"), meta);
|
1038
|
+
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
1039
|
+
|
1040
|
+
VALUE samples = rb_ary_new();
|
1041
|
+
rb_ivar_set(result, rb_intern("@samples"), samples);
|
1042
|
+
VALUE weights = rb_ary_new();
|
1043
|
+
rb_ivar_set(result, rb_intern("@weights"), weights);
|
1044
|
+
for (auto& stack_index: this->samples) {
|
1045
|
+
rb_ary_push(samples, INT2NUM(stack_index));
|
1046
|
+
rb_ary_push(weights, INT2NUM(1));
|
1047
|
+
}
|
1048
|
+
|
1049
|
+
VALUE timestamps = rb_ary_new();
|
1050
|
+
rb_ivar_set(result, rb_intern("@timestamps"), timestamps);
|
1051
|
+
|
1052
|
+
for (auto& timestamp: this->timestamps) {
|
1053
|
+
rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
|
1054
|
+
}
|
1055
|
+
|
1056
|
+
VALUE sample_threads = rb_ary_new();
|
1057
|
+
rb_ivar_set(result, rb_intern("@sample_threads"), sample_threads);
|
1058
|
+
for (auto& thread: this->sample_threads) {
|
1059
|
+
rb_ary_push(sample_threads, ULL2NUM(thread));
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
VALUE sample_categories = rb_ary_new();
|
1063
|
+
rb_ivar_set(result, rb_intern("@sample_categories"), sample_categories);
|
1064
|
+
for (auto& cat: this->sample_categories) {
|
1065
|
+
rb_ary_push(sample_categories, INT2NUM(cat));
|
1066
|
+
}
|
1067
|
+
|
1068
|
+
VALUE threads = rb_hash_new();
|
1069
|
+
rb_ivar_set(result, rb_intern("@threads"), threads);
|
1070
|
+
|
1071
|
+
for (const auto& thread: this->threads.list) {
|
1072
|
+
VALUE hash = rb_hash_new();
|
1073
|
+
rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
|
1074
|
+
rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
|
1075
|
+
rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
|
1076
|
+
if (!thread.stopped_at.zero()) {
|
1077
|
+
rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread.stopped_at.nanoseconds()));
|
1078
|
+
}
|
1079
|
+
rb_hash_aset(hash, sym("name"), rb_str_new(thread.name.data(), thread.name.length()));
|
1080
|
+
|
1081
|
+
}
|
1082
|
+
|
1083
|
+
frame_list.write_result(result);
|
1084
|
+
|
1085
|
+
return result;
|
1086
|
+
}
|
1087
|
+
|
1088
|
+
void mark() {
|
1089
|
+
frame_list.mark_frames();
|
1090
|
+
rb_gc_mark(gc_hook);
|
1091
|
+
|
1092
|
+
//for (int i = 0; i < queued_length; i++) {
|
1093
|
+
// rb_gc_mark(queued_frames[i]);
|
1094
|
+
//}
|
1095
|
+
|
1096
|
+
// FIXME: How can we best mark buffered or pending frames?
|
1097
|
+
}
|
1098
|
+
};
|
68
1099
|
|
69
1100
|
static void
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
1101
|
+
collector_mark(void *data) {
|
1102
|
+
BaseCollector *collector = static_cast<BaseCollector *>(data);
|
1103
|
+
collector->mark();
|
1104
|
+
}
|
74
1105
|
|
75
|
-
|
1106
|
+
static void
|
1107
|
+
collector_free(void *data) {
|
1108
|
+
BaseCollector *collector = static_cast<BaseCollector *>(data);
|
1109
|
+
delete collector;
|
76
1110
|
}
|
77
1111
|
|
1112
|
+
static const rb_data_type_t rb_collector_type = {
|
1113
|
+
.wrap_struct_name = "vernier/collector",
|
1114
|
+
.function = {
|
1115
|
+
//.dmemsize = rb_collector_memsize,
|
1116
|
+
.dmark = collector_mark,
|
1117
|
+
.dfree = collector_free,
|
1118
|
+
},
|
1119
|
+
};
|
78
1120
|
|
79
|
-
static VALUE
|
80
|
-
|
81
|
-
|
1121
|
+
static BaseCollector *get_collector(VALUE obj) {
|
1122
|
+
BaseCollector *collector;
|
1123
|
+
TypedData_Get_Struct(obj, BaseCollector, &rb_collector_type, collector);
|
1124
|
+
return collector;
|
1125
|
+
}
|
82
1126
|
|
83
|
-
|
84
|
-
|
1127
|
+
static VALUE
|
1128
|
+
collector_start(VALUE self) {
|
1129
|
+
auto *collector = get_collector(self);
|
85
1130
|
|
86
|
-
|
87
|
-
|
1131
|
+
if (!collector->start()) {
|
1132
|
+
rb_raise(rb_eRuntimeError, "already running");
|
1133
|
+
}
|
88
1134
|
|
89
1135
|
return Qtrue;
|
90
1136
|
}
|
91
1137
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
extern "C" size_t rb_obj_memsize_of(VALUE);
|
1138
|
+
static VALUE
|
1139
|
+
collector_stop(VALUE self) {
|
1140
|
+
auto *collector = get_collector(self);
|
96
1141
|
|
97
|
-
|
98
|
-
|
99
|
-
enum ruby_value_type type = rb_type(obj);
|
100
|
-
|
101
|
-
#define TYPE_CASE(x) case (x): return (#x)
|
102
|
-
|
103
|
-
// Many of these are impossible, but it's easier to just include them
|
104
|
-
switch (type) {
|
105
|
-
TYPE_CASE(T_OBJECT);
|
106
|
-
TYPE_CASE(T_CLASS);
|
107
|
-
TYPE_CASE(T_MODULE);
|
108
|
-
TYPE_CASE(T_FLOAT);
|
109
|
-
TYPE_CASE(T_STRING);
|
110
|
-
TYPE_CASE(T_REGEXP);
|
111
|
-
TYPE_CASE(T_ARRAY);
|
112
|
-
TYPE_CASE(T_HASH);
|
113
|
-
TYPE_CASE(T_STRUCT);
|
114
|
-
TYPE_CASE(T_BIGNUM);
|
115
|
-
TYPE_CASE(T_FILE);
|
116
|
-
TYPE_CASE(T_DATA);
|
117
|
-
TYPE_CASE(T_MATCH);
|
118
|
-
TYPE_CASE(T_COMPLEX);
|
119
|
-
TYPE_CASE(T_RATIONAL);
|
120
|
-
|
121
|
-
TYPE_CASE(T_NIL);
|
122
|
-
TYPE_CASE(T_TRUE);
|
123
|
-
TYPE_CASE(T_FALSE);
|
124
|
-
TYPE_CASE(T_SYMBOL);
|
125
|
-
TYPE_CASE(T_FIXNUM);
|
126
|
-
TYPE_CASE(T_UNDEF);
|
127
|
-
|
128
|
-
TYPE_CASE(T_IMEMO);
|
129
|
-
TYPE_CASE(T_NODE);
|
130
|
-
TYPE_CASE(T_ICLASS);
|
131
|
-
TYPE_CASE(T_ZOMBIE);
|
132
|
-
TYPE_CASE(T_MOVED);
|
133
|
-
|
134
|
-
default:
|
135
|
-
return "unknown type";
|
136
|
-
}
|
137
|
-
#undef TYPE_CASE
|
1142
|
+
VALUE result = collector->stop();
|
1143
|
+
return result;
|
138
1144
|
}
|
139
1145
|
|
140
1146
|
static VALUE
|
141
|
-
|
142
|
-
|
143
|
-
rb_tracepoint_disable(tp_freeobj);
|
1147
|
+
markers(VALUE self) {
|
1148
|
+
auto *collector = get_collector(self);
|
144
1149
|
|
145
|
-
|
1150
|
+
return collector->get_markers();
|
1151
|
+
}
|
146
1152
|
|
147
|
-
|
1153
|
+
static VALUE
|
1154
|
+
collector_sample(VALUE self) {
|
1155
|
+
auto *collector = get_collector(self);
|
148
1156
|
|
149
|
-
|
150
|
-
|
151
|
-
|
1157
|
+
collector->sample();
|
1158
|
+
return Qtrue;
|
1159
|
+
}
|
152
1160
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
1161
|
+
static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
1162
|
+
BaseCollector *collector;
|
1163
|
+
if (mode == sym("retained")) {
|
1164
|
+
collector = new RetainedCollector();
|
1165
|
+
} else if (mode == sym("custom")) {
|
1166
|
+
collector = new CustomCollector();
|
1167
|
+
} else if (mode == sym("wall")) {
|
1168
|
+
VALUE intervalv = rb_hash_aref(options, sym("interval"));
|
1169
|
+
TimeStamp interval;
|
1170
|
+
if (NIL_P(intervalv)) {
|
1171
|
+
interval = TimeStamp::from_microseconds(500);
|
1172
|
+
} else {
|
1173
|
+
interval = TimeStamp::from_microseconds(NUM2UINT(intervalv));
|
157
1174
|
}
|
158
|
-
|
159
|
-
|
1175
|
+
collector = new TimeCollector(interval);
|
1176
|
+
} else {
|
1177
|
+
rb_raise(rb_eArgError, "invalid mode");
|
160
1178
|
}
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
return str;
|
1179
|
+
VALUE obj = TypedData_Wrap_Struct(self, &rb_collector_type, collector);
|
1180
|
+
rb_funcall(obj, rb_intern("initialize"), 1, mode);
|
1181
|
+
return obj;
|
166
1182
|
}
|
167
1183
|
|
168
1184
|
static void
|
169
|
-
|
170
|
-
|
1185
|
+
Init_consts() {
|
1186
|
+
#define MARKER_CONST(name) \
|
1187
|
+
rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
|
171
1188
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
1189
|
+
MARKER_CONST(GVL_THREAD_STARTED);
|
1190
|
+
MARKER_CONST(GVL_THREAD_READY);
|
1191
|
+
MARKER_CONST(GVL_THREAD_RESUMED);
|
1192
|
+
MARKER_CONST(GVL_THREAD_SUSPENDED);
|
1193
|
+
MARKER_CONST(GVL_THREAD_EXITED);
|
1194
|
+
|
1195
|
+
MARKER_CONST(GC_START);
|
1196
|
+
MARKER_CONST(GC_END_MARK);
|
1197
|
+
MARKER_CONST(GC_END_SWEEP);
|
1198
|
+
MARKER_CONST(GC_ENTER);
|
1199
|
+
MARKER_CONST(GC_EXIT);
|
1200
|
+
|
1201
|
+
#undef MARKER_CONST
|
178
1202
|
}
|
179
1203
|
|
180
1204
|
extern "C" void
|
181
1205
|
Init_vernier(void)
|
182
1206
|
{
|
183
1207
|
rb_mVernier = rb_define_module("Vernier");
|
1208
|
+
rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
|
1209
|
+
VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
|
1210
|
+
rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
|
1211
|
+
|
1212
|
+
rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
|
1213
|
+
rb_undef_alloc_func(rb_cVernierCollector);
|
1214
|
+
rb_define_singleton_method(rb_cVernierCollector, "_new", collector_new, 2);
|
1215
|
+
rb_define_method(rb_cVernierCollector, "start", collector_start, 0);
|
1216
|
+
rb_define_method(rb_cVernierCollector, "sample", collector_sample, 0);
|
1217
|
+
rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
|
1218
|
+
rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
|
184
1219
|
|
185
|
-
|
186
|
-
rb_define_module_function(rb_mVernier, "trace_retained_stop", trace_retained_stop, 0);
|
1220
|
+
Init_consts();
|
187
1221
|
|
188
|
-
static VALUE gc_hook = Data_Wrap_Struct(rb_cObject,
|
189
|
-
rb_global_variable(&gc_hook);
|
1222
|
+
//static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
|
1223
|
+
//rb_global_variable(&gc_hook);
|
190
1224
|
}
|