vernier 1.8.1 → 1.9.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/.ruby-version +1 -1
- data/Gemfile +1 -0
- data/README.md +65 -0
- data/examples/custom_hook.rb +37 -0
- data/ext/vernier/heap_tracker.cc +277 -0
- data/ext/vernier/memory.cc +1 -1
- data/ext/vernier/stack_table.cc +290 -0
- data/ext/vernier/stack_table.hh +314 -0
- data/ext/vernier/vernier.cc +67 -791
- data/ext/vernier/vernier.hh +7 -0
- data/lib/vernier/collector.rb +112 -2
- data/lib/vernier/heap_tracker.rb +47 -0
- data/lib/vernier/memory_leak_detector.rb +40 -0
- data/lib/vernier/result.rb +37 -22
- data/lib/vernier/stack_table_helpers.rb +24 -10
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +2 -6
- data/vernier.gemspec +39 -0
- metadata +9 -2
data/ext/vernier/vernier.cc
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
#include "timestamp.hh"
|
|
20
20
|
#include "periodic_thread.hh"
|
|
21
21
|
#include "signal_safe_semaphore.hh"
|
|
22
|
+
#include "stack_table.hh"
|
|
22
23
|
|
|
23
24
|
#include "ruby/ruby.h"
|
|
24
25
|
#include "ruby/encoding.h"
|
|
@@ -43,18 +44,13 @@
|
|
|
43
44
|
RUBY_EVENT_FIBER_SWITCH | \
|
|
44
45
|
RUBY_EVENT_THREAD_END
|
|
45
46
|
|
|
46
|
-
#define sym(name) ID2SYM(rb_intern_const(name))
|
|
47
|
-
|
|
48
|
-
// HACK: This isn't public, but the objspace ext uses it
|
|
49
|
-
extern "C" size_t rb_obj_memsize_of(VALUE);
|
|
50
|
-
|
|
51
47
|
using namespace std;
|
|
52
48
|
|
|
53
49
|
VALUE rb_mVernier;
|
|
54
50
|
static VALUE rb_cVernierResult;
|
|
55
51
|
static VALUE rb_mVernierMarkerType;
|
|
56
52
|
static VALUE rb_cVernierCollector;
|
|
57
|
-
static VALUE
|
|
53
|
+
static VALUE rb_cTimeCollector;
|
|
58
54
|
|
|
59
55
|
static VALUE sym_state, sym_gc_by, sym_fiber_id;
|
|
60
56
|
|
|
@@ -74,126 +70,6 @@ static const char *gvl_event_name(rb_event_flag_t event) {
|
|
|
74
70
|
return "no-event";
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
struct FuncInfo {
|
|
78
|
-
static const char *label_cstr(VALUE frame) {
|
|
79
|
-
VALUE label = rb_profile_frame_full_label(frame);
|
|
80
|
-
// Currently (2025-03-22, Ruby 3.4.2) this occurs when an iseq method
|
|
81
|
-
// entry is replaced with a refinement
|
|
82
|
-
if (NIL_P(label)) return "(nil)";
|
|
83
|
-
return StringValueCStr(label);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static const char *file_cstr(VALUE frame) {
|
|
87
|
-
VALUE file = rb_profile_frame_absolute_path(frame);
|
|
88
|
-
if (NIL_P(file))
|
|
89
|
-
file = rb_profile_frame_path(frame);
|
|
90
|
-
if (NIL_P(file)) {
|
|
91
|
-
return "(nil)";
|
|
92
|
-
} else {
|
|
93
|
-
return StringValueCStr(file);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
static int first_lineno_int(VALUE frame) {
|
|
98
|
-
VALUE first_lineno = rb_profile_frame_first_lineno(frame);
|
|
99
|
-
return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
FuncInfo(VALUE frame) :
|
|
103
|
-
label(label_cstr(frame)),
|
|
104
|
-
file(file_cstr(frame)),
|
|
105
|
-
first_lineno(first_lineno_int(frame)) { }
|
|
106
|
-
|
|
107
|
-
std::string label;
|
|
108
|
-
std::string file;
|
|
109
|
-
int first_lineno;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
bool operator==(const FuncInfo& lhs, const FuncInfo& rhs) noexcept {
|
|
113
|
-
return
|
|
114
|
-
lhs.label == rhs.label &&
|
|
115
|
-
lhs.file == rhs.file &&
|
|
116
|
-
lhs.first_lineno == rhs.first_lineno;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
struct Frame {
|
|
120
|
-
VALUE frame;
|
|
121
|
-
int line;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
|
125
|
-
return lhs.frame == rhs.frame && lhs.line == rhs.line;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
|
|
129
|
-
return !(lhs == rhs);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
namespace std {
|
|
133
|
-
template<>
|
|
134
|
-
struct hash<Frame>
|
|
135
|
-
{
|
|
136
|
-
std::size_t operator()(Frame const& s) const noexcept
|
|
137
|
-
{
|
|
138
|
-
return s.frame ^ s.line;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
class RawSample {
|
|
144
|
-
public:
|
|
145
|
-
|
|
146
|
-
constexpr static int MAX_LEN = 2048;
|
|
147
|
-
|
|
148
|
-
private:
|
|
149
|
-
|
|
150
|
-
VALUE frames[MAX_LEN];
|
|
151
|
-
int lines[MAX_LEN];
|
|
152
|
-
int len;
|
|
153
|
-
int offset;
|
|
154
|
-
bool gc;
|
|
155
|
-
|
|
156
|
-
public:
|
|
157
|
-
|
|
158
|
-
RawSample() : len(0), gc(false), offset(0) { }
|
|
159
|
-
|
|
160
|
-
int size() const {
|
|
161
|
-
return len - offset;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
Frame frame(int i) const {
|
|
165
|
-
int idx = len - i - 1;
|
|
166
|
-
if (idx < 0) throw std::out_of_range("VERNIER BUG: index out of range");
|
|
167
|
-
const Frame frame = {frames[idx], lines[idx]};
|
|
168
|
-
return frame;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
void sample(int offset = 0) {
|
|
172
|
-
clear();
|
|
173
|
-
|
|
174
|
-
if (!ruby_native_thread_p()) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (rb_during_gc()) {
|
|
179
|
-
gc = true;
|
|
180
|
-
} else {
|
|
181
|
-
len = rb_profile_frames(0, MAX_LEN, frames, lines);
|
|
182
|
-
this->offset = std::min(offset, len);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
void clear() {
|
|
187
|
-
len = 0;
|
|
188
|
-
offset = 0;
|
|
189
|
-
gc = false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
bool empty() const {
|
|
193
|
-
return len <= offset;
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
73
|
// Based very loosely on the design of Gecko's SigHandlerCoordinator
|
|
198
74
|
// This is used for communication between the profiler thread and the signal
|
|
199
75
|
// handlers in the observed thread.
|
|
@@ -227,395 +103,6 @@ struct LiveSample {
|
|
|
227
103
|
}
|
|
228
104
|
};
|
|
229
105
|
|
|
230
|
-
template <typename K>
|
|
231
|
-
class IndexMap {
|
|
232
|
-
public:
|
|
233
|
-
std::unordered_map<K, int> to_idx;
|
|
234
|
-
std::vector<K> list;
|
|
235
|
-
|
|
236
|
-
const K& operator[](int i) const noexcept {
|
|
237
|
-
return list[i];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
size_t size() const noexcept {
|
|
241
|
-
return list.size();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
int index(const K key) {
|
|
245
|
-
auto it = to_idx.find(key);
|
|
246
|
-
if (it == to_idx.end()) {
|
|
247
|
-
int idx = list.size();
|
|
248
|
-
list.push_back(key);
|
|
249
|
-
|
|
250
|
-
auto result = to_idx.insert({key, idx});
|
|
251
|
-
it = result.first;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return it->second;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
void clear() {
|
|
258
|
-
list.clear();
|
|
259
|
-
to_idx.clear();
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
struct StackTable {
|
|
264
|
-
private:
|
|
265
|
-
|
|
266
|
-
struct FrameWithInfo {
|
|
267
|
-
Frame frame;
|
|
268
|
-
FuncInfo info;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
IndexMap<Frame> frame_map;
|
|
272
|
-
|
|
273
|
-
IndexMap<VALUE> func_map;
|
|
274
|
-
std::vector<FuncInfo> func_info_list;
|
|
275
|
-
|
|
276
|
-
struct StackNode {
|
|
277
|
-
std::unordered_map<Frame, int> children;
|
|
278
|
-
Frame frame;
|
|
279
|
-
int parent;
|
|
280
|
-
int index;
|
|
281
|
-
|
|
282
|
-
StackNode(Frame frame, int index, int parent) : frame(frame), index(index), parent(parent) {}
|
|
283
|
-
|
|
284
|
-
// root
|
|
285
|
-
StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
// This mutex guards the StackNodes only. The rest of the maps and vectors
|
|
289
|
-
// should be guarded by the GVL
|
|
290
|
-
std::mutex stack_mutex;
|
|
291
|
-
|
|
292
|
-
StackNode root_stack_node;
|
|
293
|
-
vector<StackNode> stack_node_list;
|
|
294
|
-
int stack_node_list_finalized_idx = 0;
|
|
295
|
-
|
|
296
|
-
StackNode *next_stack_node(StackNode *node, Frame frame) {
|
|
297
|
-
auto search = node->children.find(frame);
|
|
298
|
-
if (search == node->children.end()) {
|
|
299
|
-
// insert a new node
|
|
300
|
-
int next_node_idx = stack_node_list.size();
|
|
301
|
-
node->children[frame] = next_node_idx;
|
|
302
|
-
stack_node_list.emplace_back(
|
|
303
|
-
frame,
|
|
304
|
-
next_node_idx,
|
|
305
|
-
node->index
|
|
306
|
-
);
|
|
307
|
-
return &stack_node_list[next_node_idx];
|
|
308
|
-
} else {
|
|
309
|
-
int node_idx = search->second;
|
|
310
|
-
return &stack_node_list[node_idx];
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
public:
|
|
315
|
-
|
|
316
|
-
int stack_index(const RawSample &stack) {
|
|
317
|
-
if (stack.empty()) {
|
|
318
|
-
throw std::runtime_error("VERNIER BUG: empty stack");
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
322
|
-
|
|
323
|
-
StackNode *node = &root_stack_node;
|
|
324
|
-
for (int i = 0; i < stack.size(); i++) {
|
|
325
|
-
Frame frame = stack.frame(i);
|
|
326
|
-
node = next_stack_node(node, frame);
|
|
327
|
-
}
|
|
328
|
-
return node->index;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
int stack_parent(int stack_idx) {
|
|
332
|
-
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
333
|
-
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
|
334
|
-
return -1;
|
|
335
|
-
} else {
|
|
336
|
-
return stack_node_list[stack_idx].parent;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
int stack_frame(int stack_idx) {
|
|
341
|
-
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
342
|
-
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
|
343
|
-
return -1;
|
|
344
|
-
} else {
|
|
345
|
-
return frame_map.index(stack_node_list[stack_idx].frame);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
|
350
|
-
// which allocates.
|
|
351
|
-
void finalize() {
|
|
352
|
-
{
|
|
353
|
-
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
354
|
-
for (int i = stack_node_list_finalized_idx; i < stack_node_list.size(); i++) {
|
|
355
|
-
const auto &stack_node = stack_node_list[i];
|
|
356
|
-
frame_map.index(stack_node.frame);
|
|
357
|
-
func_map.index(stack_node.frame.frame);
|
|
358
|
-
stack_node_list_finalized_idx = i;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
for (int i = func_info_list.size(); i < func_map.size(); i++) {
|
|
363
|
-
const auto &func = func_map[i];
|
|
364
|
-
// must not hold a mutex here
|
|
365
|
-
func_info_list.push_back(FuncInfo(func));
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
void mark_frames() {
|
|
370
|
-
const std::lock_guard<std::mutex> lock(stack_mutex);
|
|
371
|
-
|
|
372
|
-
for (auto stack_node: stack_node_list) {
|
|
373
|
-
rb_gc_mark(stack_node.frame.frame);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
StackNode *convert_stack(StackTable &other, int original_idx) {
|
|
378
|
-
if (original_idx < 0) {
|
|
379
|
-
return &root_stack_node;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
StackNode &original_node = other.stack_node_list[original_idx];
|
|
383
|
-
StackNode *parent_node = convert_stack(other, original_node.parent);
|
|
384
|
-
StackNode *node = next_stack_node(parent_node, original_node.frame);
|
|
385
|
-
|
|
386
|
-
return node;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
static VALUE stack_table_convert(VALUE self, VALUE other, VALUE original_stack);
|
|
390
|
-
|
|
391
|
-
static VALUE stack_table_stack_count(VALUE self);
|
|
392
|
-
static VALUE stack_table_frame_count(VALUE self);
|
|
393
|
-
static VALUE stack_table_func_count(VALUE self);
|
|
394
|
-
|
|
395
|
-
static VALUE stack_table_frame_line_no(VALUE self, VALUE idxval);
|
|
396
|
-
static VALUE stack_table_frame_func_idx(VALUE self, VALUE idxval);
|
|
397
|
-
static VALUE stack_table_func_name(VALUE self, VALUE idxval);
|
|
398
|
-
static VALUE stack_table_func_filename(VALUE self, VALUE idxval);
|
|
399
|
-
static VALUE stack_table_func_first_lineno(VALUE self, VALUE idxval);
|
|
400
|
-
|
|
401
|
-
friend class SampleTranslator;
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
static void
|
|
405
|
-
stack_table_mark(void *data) {
|
|
406
|
-
StackTable *stack_table = static_cast<StackTable *>(data);
|
|
407
|
-
stack_table->mark_frames();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
static void
|
|
411
|
-
stack_table_free(void *data) {
|
|
412
|
-
StackTable *stack_table = static_cast<StackTable *>(data);
|
|
413
|
-
delete stack_table;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
static const rb_data_type_t rb_stack_table_type = {
|
|
417
|
-
.wrap_struct_name = "vernier/stack_table",
|
|
418
|
-
.function = {
|
|
419
|
-
//.dmemsize = rb_collector_memsize,
|
|
420
|
-
.dmark = stack_table_mark,
|
|
421
|
-
.dfree = stack_table_free,
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
static VALUE
|
|
426
|
-
stack_table_new(VALUE self) {
|
|
427
|
-
StackTable *stack_table = new StackTable();
|
|
428
|
-
VALUE obj = TypedData_Wrap_Struct(self, &rb_stack_table_type, stack_table);
|
|
429
|
-
return obj;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
static StackTable *get_stack_table(VALUE obj) {
|
|
433
|
-
StackTable *stack_table;
|
|
434
|
-
TypedData_Get_Struct(obj, StackTable, &rb_stack_table_type, stack_table);
|
|
435
|
-
return stack_table;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
static VALUE
|
|
439
|
-
stack_table_current_stack(int argc, VALUE *argv, VALUE self) {
|
|
440
|
-
int offset;
|
|
441
|
-
VALUE offset_v;
|
|
442
|
-
|
|
443
|
-
rb_scan_args(argc, argv, "01", &offset_v);
|
|
444
|
-
if (argc > 0) {
|
|
445
|
-
offset = NUM2INT(offset_v) + 1;
|
|
446
|
-
} else {
|
|
447
|
-
offset = 1;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
StackTable *stack_table = get_stack_table(self);
|
|
451
|
-
RawSample stack;
|
|
452
|
-
stack.sample(offset);
|
|
453
|
-
int stack_index = stack_table->stack_index(stack);
|
|
454
|
-
return INT2NUM(stack_index);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
static VALUE
|
|
458
|
-
stack_table_stack_parent_idx(VALUE self, VALUE idxval) {
|
|
459
|
-
StackTable *stack_table = get_stack_table(self);
|
|
460
|
-
int idx = NUM2INT(idxval);
|
|
461
|
-
int parent_idx = stack_table->stack_parent(idx);
|
|
462
|
-
if (parent_idx < 0) {
|
|
463
|
-
return Qnil;
|
|
464
|
-
} else {
|
|
465
|
-
return INT2NUM(parent_idx);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
static VALUE
|
|
470
|
-
stack_table_stack_frame_idx(VALUE self, VALUE idxval) {
|
|
471
|
-
StackTable *stack_table = get_stack_table(self);
|
|
472
|
-
//stack_table->finalize();
|
|
473
|
-
int idx = NUM2INT(idxval);
|
|
474
|
-
int frame_idx = stack_table->stack_frame(idx);
|
|
475
|
-
return frame_idx < 0 ? Qnil : INT2NUM(frame_idx);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
VALUE
|
|
479
|
-
StackTable::stack_table_stack_count(VALUE self) {
|
|
480
|
-
StackTable *stack_table = get_stack_table(self);
|
|
481
|
-
int count;
|
|
482
|
-
{
|
|
483
|
-
const std::lock_guard<std::mutex> lock(stack_table->stack_mutex);
|
|
484
|
-
count = stack_table->stack_node_list.size();
|
|
485
|
-
}
|
|
486
|
-
return INT2NUM(count);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
VALUE
|
|
490
|
-
StackTable::stack_table_convert(VALUE self, VALUE original_tableval, VALUE original_idxval) {
|
|
491
|
-
StackTable *stack_table = get_stack_table(self);
|
|
492
|
-
StackTable *original_table = get_stack_table(original_tableval);
|
|
493
|
-
int original_idx = NUM2INT(original_idxval);
|
|
494
|
-
|
|
495
|
-
int original_size;
|
|
496
|
-
{
|
|
497
|
-
const std::lock_guard<std::mutex> lock(original_table->stack_mutex);
|
|
498
|
-
original_size = original_table->stack_node_list.size();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (original_idx >= original_size || original_idx < 0) {
|
|
502
|
-
rb_raise(rb_eRangeError, "index out of range");
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
int result_idx;
|
|
506
|
-
{
|
|
507
|
-
const std::lock_guard<std::mutex> lock1(stack_table->stack_mutex);
|
|
508
|
-
const std::lock_guard<std::mutex> lock2(original_table->stack_mutex);
|
|
509
|
-
StackNode *node = stack_table->convert_stack(*original_table, original_idx);
|
|
510
|
-
result_idx = node->index;
|
|
511
|
-
}
|
|
512
|
-
return INT2NUM(result_idx);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
VALUE
|
|
516
|
-
StackTable::stack_table_frame_count(VALUE self) {
|
|
517
|
-
StackTable *stack_table = get_stack_table(self);
|
|
518
|
-
stack_table->finalize();
|
|
519
|
-
int count = stack_table->frame_map.size();
|
|
520
|
-
return INT2NUM(count);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
VALUE
|
|
524
|
-
StackTable::stack_table_func_count(VALUE self) {
|
|
525
|
-
StackTable *stack_table = get_stack_table(self);
|
|
526
|
-
stack_table->finalize();
|
|
527
|
-
int count = stack_table->func_map.size();
|
|
528
|
-
return INT2NUM(count);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
VALUE
|
|
532
|
-
StackTable::stack_table_frame_line_no(VALUE self, VALUE idxval) {
|
|
533
|
-
StackTable *stack_table = get_stack_table(self);
|
|
534
|
-
stack_table->finalize();
|
|
535
|
-
int idx = NUM2INT(idxval);
|
|
536
|
-
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
|
537
|
-
return Qnil;
|
|
538
|
-
} else {
|
|
539
|
-
const auto &frame = stack_table->frame_map[idx];
|
|
540
|
-
return INT2NUM(frame.line);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
VALUE
|
|
545
|
-
StackTable::stack_table_frame_func_idx(VALUE self, VALUE idxval) {
|
|
546
|
-
StackTable *stack_table = get_stack_table(self);
|
|
547
|
-
stack_table->finalize();
|
|
548
|
-
int idx = NUM2INT(idxval);
|
|
549
|
-
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
|
550
|
-
return Qnil;
|
|
551
|
-
} else {
|
|
552
|
-
const auto &frame = stack_table->frame_map[idx];
|
|
553
|
-
int func_idx = stack_table->func_map.index(frame.frame);
|
|
554
|
-
return INT2NUM(func_idx);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
VALUE
|
|
559
|
-
StackTable::stack_table_func_name(VALUE self, VALUE idxval) {
|
|
560
|
-
StackTable *stack_table = get_stack_table(self);
|
|
561
|
-
stack_table->finalize();
|
|
562
|
-
int idx = NUM2INT(idxval);
|
|
563
|
-
auto &table = stack_table->func_info_list;
|
|
564
|
-
if (idx < 0 || idx >= table.size()) {
|
|
565
|
-
return Qnil;
|
|
566
|
-
} else {
|
|
567
|
-
const auto &func_info = table[idx];
|
|
568
|
-
const std::string &label = func_info.label;
|
|
569
|
-
|
|
570
|
-
// Ruby constants are in an arbitrary (ASCII compatible) encoding and
|
|
571
|
-
// method names are in an arbitrary (ASCII compatible) encoding. These
|
|
572
|
-
// can be mixed in the same program.
|
|
573
|
-
//
|
|
574
|
-
// However, by this point we've lost the chain of what the correct
|
|
575
|
-
// encoding should be. Oops!
|
|
576
|
-
//
|
|
577
|
-
// Instead we'll just guess at UTF-8 which should satisfy most. It won't
|
|
578
|
-
// necessarily be valid but that can be scrubbed on the Ruby side.
|
|
579
|
-
//
|
|
580
|
-
// In the future we might keep class and method name separate for
|
|
581
|
-
// longer, preserve encodings, and defer formatting to the Ruby side.
|
|
582
|
-
return rb_enc_interned_str(label.c_str(), label.length(), rb_utf8_encoding());
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
VALUE
|
|
587
|
-
StackTable::stack_table_func_filename(VALUE self, VALUE idxval) {
|
|
588
|
-
StackTable *stack_table = get_stack_table(self);
|
|
589
|
-
stack_table->finalize();
|
|
590
|
-
int idx = NUM2INT(idxval);
|
|
591
|
-
auto &table = stack_table->func_info_list;
|
|
592
|
-
if (idx < 0 || idx >= table.size()) {
|
|
593
|
-
return Qnil;
|
|
594
|
-
} else {
|
|
595
|
-
const auto &func_info = table[idx];
|
|
596
|
-
const std::string &filename = func_info.file;
|
|
597
|
-
|
|
598
|
-
// Technically filesystems are binary and then Ruby interprets that as
|
|
599
|
-
// default_external encoding. But to keep things simple for now we are
|
|
600
|
-
// going to assume UTF-8.
|
|
601
|
-
return rb_enc_interned_str(filename.c_str(), filename.length(), rb_utf8_encoding());
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
VALUE
|
|
606
|
-
StackTable::stack_table_func_first_lineno(VALUE self, VALUE idxval) {
|
|
607
|
-
StackTable *stack_table = get_stack_table(self);
|
|
608
|
-
stack_table->finalize();
|
|
609
|
-
int idx = NUM2INT(idxval);
|
|
610
|
-
auto &table = stack_table->func_info_list;
|
|
611
|
-
if (idx < 0 || idx >= table.size()) {
|
|
612
|
-
return Qnil;
|
|
613
|
-
} else {
|
|
614
|
-
const auto &func_info = table[idx];
|
|
615
|
-
return INT2NUM(func_info.first_lineno);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
106
|
class SampleTranslator {
|
|
620
107
|
public:
|
|
621
108
|
int last_stack_index;
|
|
@@ -956,8 +443,7 @@ class Thread {
|
|
|
956
443
|
unique_ptr<MarkerTable> markers;
|
|
957
444
|
|
|
958
445
|
// FIXME: don't use pthread at start
|
|
959
|
-
Thread(State state, pthread_t pthread_id, VALUE ruby_thread) : pthread_id(pthread_id), ruby_thread(ruby_thread), state(state), stack_on_suspend_idx(-1) {
|
|
960
|
-
ruby_thread_id = rb_obj_id(ruby_thread);
|
|
446
|
+
Thread(State state, pthread_t pthread_id, VALUE ruby_thread, VALUE ruby_thread_id) : pthread_id(pthread_id), ruby_thread(ruby_thread), state(state), stack_on_suspend_idx(-1), ruby_thread_id(ruby_thread_id) {
|
|
961
447
|
//ruby_thread_id = ULL2NUM(ruby_thread);
|
|
962
448
|
native_tid = get_native_thread_id();
|
|
963
449
|
started_at = state_changed_at = TimeStamp::Now();
|
|
@@ -989,7 +475,7 @@ class Thread {
|
|
|
989
475
|
markers->record(Marker::Type::MARKER_FIBER_SWITCH, stack_idx, { .fiber_data = { .fiber_id = fiber_id } });
|
|
990
476
|
}
|
|
991
477
|
|
|
992
|
-
void set_state(State new_state) {
|
|
478
|
+
void set_state(State new_state, bool current = true) {
|
|
993
479
|
if (state == Thread::State::STOPPED) {
|
|
994
480
|
return;
|
|
995
481
|
}
|
|
@@ -1117,9 +603,43 @@ class ThreadTable {
|
|
|
1117
603
|
}
|
|
1118
604
|
|
|
1119
605
|
private:
|
|
1120
|
-
|
|
606
|
+
Thread* find_thread(VALUE th) {
|
|
607
|
+
// Assumes lock is already held by caller
|
|
608
|
+
for (auto &threadptr : list) {
|
|
609
|
+
if (thread_equal(th, threadptr->ruby_thread)) {
|
|
610
|
+
return threadptr.get();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return nullptr;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
Thread& find_or_create_thread(VALUE th, Thread::State initial_state) {
|
|
617
|
+
{
|
|
618
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
|
619
|
+
Thread* existing = find_thread(th);
|
|
620
|
+
if (existing) {
|
|
621
|
+
return *existing;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Thread not found, compute ID outside the lock
|
|
626
|
+
VALUE ruby_thread_id = rb_obj_id(th);
|
|
627
|
+
|
|
1121
628
|
const std::lock_guard<std::mutex> lock(mutex);
|
|
629
|
+
// Check again in case another thread created it while we were unlocked
|
|
630
|
+
Thread* existing = find_thread(th);
|
|
631
|
+
if (existing) {
|
|
632
|
+
return *existing;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
//fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, initial_state);
|
|
636
|
+
auto new_thread = std::make_unique<Thread>(initial_state, pthread_self(), th, ruby_thread_id);
|
|
637
|
+
Thread& thread_ref = *new_thread;
|
|
638
|
+
list.push_back(std::move(new_thread));
|
|
639
|
+
return thread_ref;
|
|
640
|
+
}
|
|
1122
641
|
|
|
642
|
+
void set_state(Thread::State new_state, VALUE th) {
|
|
1123
643
|
//cerr << "set state=" << new_state << " thread=" << gettid() << endl;
|
|
1124
644
|
|
|
1125
645
|
pid_t native_tid = get_native_thread_id();
|
|
@@ -1127,35 +647,28 @@ class ThreadTable {
|
|
|
1127
647
|
|
|
1128
648
|
//fprintf(stderr, "th %p (tid: %i) from %s to %s\n", (void *)th, native_tid, gvl_event_name(state), gvl_event_name(new_state));
|
|
1129
649
|
|
|
1130
|
-
|
|
1131
|
-
auto &thread = *threadptr;
|
|
1132
|
-
if (thread_equal(th, thread.ruby_thread)) {
|
|
1133
|
-
if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
|
|
650
|
+
Thread& thread = find_or_create_thread(th, new_state);
|
|
1134
651
|
|
|
1135
|
-
|
|
1136
|
-
sample.sample();
|
|
652
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
|
1137
653
|
|
|
1138
|
-
|
|
1139
|
-
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
|
1140
|
-
}
|
|
654
|
+
if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
|
|
1141
655
|
|
|
1142
|
-
|
|
656
|
+
RawSample sample;
|
|
657
|
+
sample.sample();
|
|
1143
658
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
} else {
|
|
1148
|
-
thread.pthread_id = 0;
|
|
1149
|
-
thread.native_tid = 0;
|
|
1150
|
-
}
|
|
659
|
+
thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
|
|
660
|
+
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
|
661
|
+
}
|
|
1151
662
|
|
|
663
|
+
thread.set_state(new_state);
|
|
1152
664
|
|
|
1153
|
-
|
|
1154
|
-
|
|
665
|
+
if (thread.state == Thread::State::RUNNING) {
|
|
666
|
+
thread.pthread_id = pthread_self();
|
|
667
|
+
thread.native_tid = get_native_thread_id();
|
|
668
|
+
} else {
|
|
669
|
+
thread.pthread_id = 0;
|
|
670
|
+
thread.native_tid = 0;
|
|
1155
671
|
}
|
|
1156
|
-
|
|
1157
|
-
//fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, new_state);
|
|
1158
|
-
list.push_back(std::make_unique<Thread>(new_state, pthread_self(), th));
|
|
1159
672
|
}
|
|
1160
673
|
|
|
1161
674
|
bool thread_equal(VALUE a, VALUE b) {
|
|
@@ -1232,209 +745,6 @@ class BaseCollector {
|
|
|
1232
745
|
};
|
|
1233
746
|
};
|
|
1234
747
|
|
|
1235
|
-
class CustomCollector : public BaseCollector {
|
|
1236
|
-
SampleList samples;
|
|
1237
|
-
|
|
1238
|
-
void sample() {
|
|
1239
|
-
RawSample sample;
|
|
1240
|
-
sample.sample();
|
|
1241
|
-
int stack_index = stack_table->stack_index(sample);
|
|
1242
|
-
|
|
1243
|
-
samples.record_sample(stack_index, TimeStamp::Now(), CATEGORY_NORMAL);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
VALUE stop() {
|
|
1247
|
-
BaseCollector::stop();
|
|
1248
|
-
|
|
1249
|
-
stack_table->finalize();
|
|
1250
|
-
|
|
1251
|
-
VALUE result = build_collector_result();
|
|
1252
|
-
|
|
1253
|
-
reset();
|
|
1254
|
-
|
|
1255
|
-
return result;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
VALUE build_collector_result() {
|
|
1259
|
-
VALUE result = BaseCollector::build_collector_result();
|
|
1260
|
-
|
|
1261
|
-
VALUE threads = rb_hash_new();
|
|
1262
|
-
rb_ivar_set(result, rb_intern("@threads"), threads);
|
|
1263
|
-
|
|
1264
|
-
VALUE thread_hash = rb_hash_new();
|
|
1265
|
-
samples.write_result(thread_hash);
|
|
1266
|
-
|
|
1267
|
-
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
|
1268
|
-
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
|
1269
|
-
rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("custom"));
|
|
1270
|
-
rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
|
1271
|
-
|
|
1272
|
-
return result;
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
public:
|
|
1276
|
-
|
|
1277
|
-
CustomCollector(VALUE stack_table) : BaseCollector(stack_table) { }
|
|
1278
|
-
};
|
|
1279
|
-
|
|
1280
|
-
class RetainedCollector : public BaseCollector {
|
|
1281
|
-
void reset() {
|
|
1282
|
-
object_frames.clear();
|
|
1283
|
-
object_list.clear();
|
|
1284
|
-
|
|
1285
|
-
BaseCollector::reset();
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
void record(VALUE obj) {
|
|
1289
|
-
RawSample sample;
|
|
1290
|
-
sample.sample();
|
|
1291
|
-
if (sample.empty()) {
|
|
1292
|
-
// During thread allocation we allocate one object without a frame
|
|
1293
|
-
// (as of Ruby 3.3)
|
|
1294
|
-
// Ideally we'd allow empty samples to be represented
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
int stack_index = stack_table->stack_index(sample);
|
|
1298
|
-
|
|
1299
|
-
object_list.push_back(obj);
|
|
1300
|
-
object_frames.emplace(obj, stack_index);
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
std::unordered_map<VALUE, int> object_frames;
|
|
1304
|
-
std::vector<VALUE> object_list;
|
|
1305
|
-
|
|
1306
|
-
VALUE tp_newobj = Qnil;
|
|
1307
|
-
VALUE tp_freeobj = Qnil;
|
|
1308
|
-
|
|
1309
|
-
static void newobj_i(VALUE tpval, void *data) {
|
|
1310
|
-
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
|
1311
|
-
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
|
1312
|
-
VALUE obj = rb_tracearg_object(tparg);
|
|
1313
|
-
|
|
1314
|
-
collector->record(obj);
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
static void freeobj_i(VALUE tpval, void *data) {
|
|
1318
|
-
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
|
1319
|
-
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
|
1320
|
-
VALUE obj = rb_tracearg_object(tparg);
|
|
1321
|
-
|
|
1322
|
-
collector->object_frames.erase(obj);
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
public:
|
|
1326
|
-
|
|
1327
|
-
RetainedCollector(VALUE stack_table) : BaseCollector(stack_table) { }
|
|
1328
|
-
|
|
1329
|
-
bool start() {
|
|
1330
|
-
if (!BaseCollector::start()) {
|
|
1331
|
-
return false;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
|
|
1335
|
-
tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, this);
|
|
1336
|
-
|
|
1337
|
-
rb_tracepoint_enable(tp_newobj);
|
|
1338
|
-
rb_tracepoint_enable(tp_freeobj);
|
|
1339
|
-
|
|
1340
|
-
return true;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
VALUE stop() {
|
|
1344
|
-
BaseCollector::stop();
|
|
1345
|
-
|
|
1346
|
-
// GC before we start turning stacks into strings
|
|
1347
|
-
rb_gc();
|
|
1348
|
-
|
|
1349
|
-
// Stop tracking any more new objects, but we'll continue tracking free'd
|
|
1350
|
-
// objects as we may be able to free some as we remove our own references
|
|
1351
|
-
// to stack frames.
|
|
1352
|
-
rb_tracepoint_disable(tp_newobj);
|
|
1353
|
-
tp_newobj = Qnil;
|
|
1354
|
-
|
|
1355
|
-
stack_table->finalize();
|
|
1356
|
-
|
|
1357
|
-
// We should have collected info for all our frames, so no need to continue
|
|
1358
|
-
// marking them
|
|
1359
|
-
// FIXME: previously here we cleared the list of frames so we would stop
|
|
1360
|
-
// marking them. Maybe now we should set a flag so that we stop marking them
|
|
1361
|
-
|
|
1362
|
-
// GC again
|
|
1363
|
-
rb_gc();
|
|
1364
|
-
|
|
1365
|
-
rb_tracepoint_disable(tp_freeobj);
|
|
1366
|
-
tp_freeobj = Qnil;
|
|
1367
|
-
|
|
1368
|
-
VALUE result = build_collector_result();
|
|
1369
|
-
|
|
1370
|
-
reset();
|
|
1371
|
-
|
|
1372
|
-
return result;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
VALUE build_collector_result() {
|
|
1376
|
-
RetainedCollector *collector = this;
|
|
1377
|
-
StackTable &frame_list = *collector->stack_table;
|
|
1378
|
-
|
|
1379
|
-
VALUE result = BaseCollector::build_collector_result();
|
|
1380
|
-
|
|
1381
|
-
VALUE threads = rb_hash_new();
|
|
1382
|
-
rb_ivar_set(result, rb_intern("@threads"), threads);
|
|
1383
|
-
VALUE thread_hash = rb_hash_new();
|
|
1384
|
-
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
|
1385
|
-
|
|
1386
|
-
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
|
1387
|
-
VALUE samples = rb_ary_new();
|
|
1388
|
-
rb_hash_aset(thread_hash, sym("samples"), samples);
|
|
1389
|
-
VALUE weights = rb_ary_new();
|
|
1390
|
-
rb_hash_aset(thread_hash, sym("weights"), weights);
|
|
1391
|
-
|
|
1392
|
-
rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("retained memory"));
|
|
1393
|
-
rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(collector->started_at.nanoseconds()));
|
|
1394
|
-
|
|
1395
|
-
for (auto& obj: collector->object_list) {
|
|
1396
|
-
const auto search = collector->object_frames.find(obj);
|
|
1397
|
-
if (search != collector->object_frames.end()) {
|
|
1398
|
-
int stack_index = search->second;
|
|
1399
|
-
|
|
1400
|
-
rb_ary_push(samples, INT2NUM(stack_index));
|
|
1401
|
-
rb_ary_push(weights, INT2NUM(rb_obj_memsize_of(obj)));
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
return result;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
void mark() {
|
|
1409
|
-
// We don't mark the objects, but we MUST mark the frames, otherwise they
|
|
1410
|
-
// can be garbage collected.
|
|
1411
|
-
// When we stop collection we will stringify the remaining frames, and then
|
|
1412
|
-
// clear them from the set, allowing them to be removed from out output.
|
|
1413
|
-
stack_table->mark_frames();
|
|
1414
|
-
rb_gc_mark(stack_table_value);
|
|
1415
|
-
|
|
1416
|
-
rb_gc_mark(tp_newobj);
|
|
1417
|
-
rb_gc_mark(tp_freeobj);
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
void compact() {
|
|
1421
|
-
RetainedCollector *collector = this;
|
|
1422
|
-
for (auto& obj: collector->object_list) {
|
|
1423
|
-
VALUE reloc_obj = rb_gc_location(obj);
|
|
1424
|
-
|
|
1425
|
-
const auto search = collector->object_frames.find(obj);
|
|
1426
|
-
if (search != collector->object_frames.end()) {
|
|
1427
|
-
int stack_index = search->second;
|
|
1428
|
-
|
|
1429
|
-
collector->object_frames.erase(search);
|
|
1430
|
-
collector->object_frames.emplace(reloc_obj, stack_index);
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
obj = reloc_obj;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
};
|
|
1437
|
-
|
|
1438
748
|
class GlobalSignalHandler {
|
|
1439
749
|
static LiveSample *live_sample;
|
|
1440
750
|
|
|
@@ -1782,11 +1092,11 @@ class TimeCollector : public BaseCollector {
|
|
|
1782
1092
|
VALUE build_collector_result() {
|
|
1783
1093
|
VALUE result = BaseCollector::build_collector_result();
|
|
1784
1094
|
|
|
1785
|
-
rb_ivar_set(result, rb_intern("@gc_markers"), this->gc_markers.to_array());
|
|
1786
|
-
|
|
1787
1095
|
VALUE threads = rb_hash_new();
|
|
1788
1096
|
rb_ivar_set(result, rb_intern("@threads"), threads);
|
|
1789
1097
|
|
|
1098
|
+
rb_ivar_set(result, rb_intern("@gc_markers"), this->gc_markers.to_array());
|
|
1099
|
+
|
|
1790
1100
|
for (const auto& thread: this->threads.list) {
|
|
1791
1101
|
VALUE hash = rb_hash_new();
|
|
1792
1102
|
thread->samples.write_result(hash);
|
|
@@ -1840,10 +1150,9 @@ collector_compact(void *data) {
|
|
|
1840
1150
|
static const rb_data_type_t rb_collector_type = {
|
|
1841
1151
|
.wrap_struct_name = "vernier/collector",
|
|
1842
1152
|
.function = {
|
|
1843
|
-
//.dmemsize = rb_collector_memsize,
|
|
1844
1153
|
.dmark = collector_mark,
|
|
1845
1154
|
.dfree = collector_free,
|
|
1846
|
-
.dsize =
|
|
1155
|
+
.dsize = nullptr,
|
|
1847
1156
|
.dcompact = collector_compact,
|
|
1848
1157
|
},
|
|
1849
1158
|
};
|
|
@@ -1873,30 +1182,11 @@ collector_stop(VALUE self) {
|
|
|
1873
1182
|
return result;
|
|
1874
1183
|
}
|
|
1875
1184
|
|
|
1876
|
-
static VALUE
|
|
1877
|
-
collector_sample(VALUE self) {
|
|
1878
|
-
auto *collector = get_collector(self);
|
|
1879
|
-
|
|
1880
|
-
collector->sample();
|
|
1881
|
-
return Qtrue;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
static VALUE
|
|
1885
|
-
collector_stack_table(VALUE self) {
|
|
1886
|
-
auto *collector = get_collector(self);
|
|
1887
|
-
|
|
1888
|
-
return collector->stack_table_value;
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
1185
|
static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
1892
1186
|
BaseCollector *collector;
|
|
1893
1187
|
|
|
1894
|
-
VALUE stack_table = stack_table_new(
|
|
1895
|
-
if (mode == sym("
|
|
1896
|
-
collector = new RetainedCollector(stack_table);
|
|
1897
|
-
} else if (mode == sym("custom")) {
|
|
1898
|
-
collector = new CustomCollector(stack_table);
|
|
1899
|
-
} else if (mode == sym("wall")) {
|
|
1188
|
+
VALUE stack_table = StackTable::stack_table_new();
|
|
1189
|
+
if (mode == sym("wall")) {
|
|
1900
1190
|
VALUE intervalv = rb_hash_aref(options, sym("interval"));
|
|
1901
1191
|
TimeStamp interval;
|
|
1902
1192
|
if (NIL_P(intervalv)) {
|
|
@@ -1920,6 +1210,7 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
|
1920
1210
|
rb_raise(rb_eArgError, "invalid mode");
|
|
1921
1211
|
}
|
|
1922
1212
|
VALUE obj = TypedData_Wrap_Struct(self, &rb_collector_type, collector);
|
|
1213
|
+
rb_ivar_set(obj, rb_intern("@stack_table"), stack_table);
|
|
1923
1214
|
rb_funcall(obj, rb_intern("initialize"), 2, mode, options);
|
|
1924
1215
|
return obj;
|
|
1925
1216
|
}
|
|
@@ -1956,31 +1247,16 @@ Init_vernier(void)
|
|
|
1956
1247
|
rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
|
|
1957
1248
|
|
|
1958
1249
|
rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
rb_define_method(
|
|
1963
|
-
|
|
1964
|
-
rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
|
|
1965
|
-
|
|
1966
|
-
rb_cStackTable = rb_define_class_under(rb_mVernier, "StackTable", rb_cObject);
|
|
1967
|
-
rb_undef_alloc_func(rb_cStackTable);
|
|
1968
|
-
rb_define_singleton_method(rb_cStackTable, "new", stack_table_new, 0);
|
|
1969
|
-
rb_define_method(rb_cStackTable, "current_stack", stack_table_current_stack, -1);
|
|
1970
|
-
rb_define_method(rb_cStackTable, "convert", StackTable::stack_table_convert, 2);
|
|
1971
|
-
rb_define_method(rb_cStackTable, "stack_parent_idx", stack_table_stack_parent_idx, 1);
|
|
1972
|
-
rb_define_method(rb_cStackTable, "stack_frame_idx", stack_table_stack_frame_idx, 1);
|
|
1973
|
-
rb_define_method(rb_cStackTable, "frame_line_no", StackTable::stack_table_frame_line_no, 1);
|
|
1974
|
-
rb_define_method(rb_cStackTable, "frame_func_idx", StackTable::stack_table_frame_func_idx, 1);
|
|
1975
|
-
rb_define_method(rb_cStackTable, "func_name", StackTable::stack_table_func_name, 1);
|
|
1976
|
-
rb_define_method(rb_cStackTable, "func_filename", StackTable::stack_table_func_filename, 1);
|
|
1977
|
-
rb_define_method(rb_cStackTable, "func_first_lineno", StackTable::stack_table_func_first_lineno, 1);
|
|
1978
|
-
rb_define_method(rb_cStackTable, "stack_count", StackTable::stack_table_stack_count, 0);
|
|
1979
|
-
rb_define_method(rb_cStackTable, "frame_count", StackTable::stack_table_frame_count, 0);
|
|
1980
|
-
rb_define_method(rb_cStackTable, "func_count", StackTable::stack_table_func_count, 0);
|
|
1250
|
+
rb_cTimeCollector = rb_define_class_under(rb_cVernierCollector, "TimeCollector", rb_cVernierCollector);
|
|
1251
|
+
rb_undef_alloc_func(rb_cTimeCollector);
|
|
1252
|
+
rb_define_singleton_method(rb_cTimeCollector, "new", collector_new, 2);
|
|
1253
|
+
rb_define_method(rb_cTimeCollector, "start", collector_start, 0);
|
|
1254
|
+
rb_define_private_method(rb_cTimeCollector, "finish", collector_stop, 0);
|
|
1981
1255
|
|
|
1982
1256
|
Init_consts(rb_mVernierMarkerPhase);
|
|
1983
1257
|
Init_memory();
|
|
1258
|
+
Init_stack_table();
|
|
1259
|
+
Init_heap_tracker();
|
|
1984
1260
|
|
|
1985
1261
|
//static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
|
|
1986
1262
|
//rb_global_variable(&gc_hook);
|