vernier 0.6.0 → 0.7.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/Gemfile +1 -0
- data/README.md +20 -4
- data/examples/gvl_sleep.rb +62 -0
- data/examples/measure_overhead.rb +39 -0
- data/ext/vernier/vernier.cc +366 -147
- data/lib/vernier/collector.rb +12 -1
- data/lib/vernier/middleware.rb +32 -0
- data/lib/vernier/output/firefox.rb +26 -20
- data/lib/vernier/result.rb +12 -1
- data/lib/vernier/stack_table.rb +42 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +29 -16
- data/vernier.gemspec +1 -0
- metadata +21 -3
data/ext/vernier/vernier.cc
CHANGED
@@ -57,6 +57,7 @@ static VALUE rb_mVernier;
|
|
57
57
|
static VALUE rb_cVernierResult;
|
58
58
|
static VALUE rb_mVernierMarkerType;
|
59
59
|
static VALUE rb_cVernierCollector;
|
60
|
+
static VALUE rb_cStackTable;
|
60
61
|
|
61
62
|
static const char *gvl_event_name(rb_event_flag_t event) {
|
62
63
|
switch (event) {
|
@@ -197,6 +198,7 @@ std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
|
|
197
198
|
return os;
|
198
199
|
}
|
199
200
|
|
201
|
+
// TODO: Rename FuncInfo
|
200
202
|
struct FrameInfo {
|
201
203
|
static const char *label_cstr(VALUE frame) {
|
202
204
|
VALUE label = rb_profile_frame_full_label(frame);
|
@@ -239,10 +241,6 @@ bool operator==(const FrameInfo& lhs, const FrameInfo& rhs) noexcept {
|
|
239
241
|
struct Frame {
|
240
242
|
VALUE frame;
|
241
243
|
int line;
|
242
|
-
|
243
|
-
FrameInfo info() const {
|
244
|
-
return FrameInfo(frame);
|
245
|
-
}
|
246
244
|
};
|
247
245
|
|
248
246
|
bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
@@ -266,7 +264,7 @@ namespace std {
|
|
266
264
|
|
267
265
|
// A basic semaphore built on sem_wait/sem_post
|
268
266
|
// post() is guaranteed to be async-signal-safe
|
269
|
-
class
|
267
|
+
class SignalSafeSemaphore {
|
270
268
|
#ifdef __APPLE__
|
271
269
|
dispatch_semaphore_t sem;
|
272
270
|
#else
|
@@ -275,7 +273,7 @@ class SamplerSemaphore {
|
|
275
273
|
|
276
274
|
public:
|
277
275
|
|
278
|
-
|
276
|
+
SignalSafeSemaphore(unsigned int value = 0) {
|
279
277
|
#ifdef __APPLE__
|
280
278
|
sem = dispatch_semaphore_create(value);
|
281
279
|
#else
|
@@ -283,7 +281,7 @@ class SamplerSemaphore {
|
|
283
281
|
#endif
|
284
282
|
};
|
285
283
|
|
286
|
-
~
|
284
|
+
~SignalSafeSemaphore() {
|
287
285
|
#ifdef __APPLE__
|
288
286
|
dispatch_release(sem);
|
289
287
|
#else
|
@@ -316,17 +314,25 @@ class SamplerSemaphore {
|
|
316
314
|
}
|
317
315
|
};
|
318
316
|
|
319
|
-
|
317
|
+
class RawSample {
|
318
|
+
public:
|
319
|
+
|
320
320
|
constexpr static int MAX_LEN = 2048;
|
321
|
+
|
322
|
+
private:
|
323
|
+
|
321
324
|
VALUE frames[MAX_LEN];
|
322
325
|
int lines[MAX_LEN];
|
323
326
|
int len;
|
327
|
+
int offset;
|
324
328
|
bool gc;
|
325
329
|
|
326
|
-
|
330
|
+
public:
|
331
|
+
|
332
|
+
RawSample() : len(0), gc(false), offset(0) { }
|
327
333
|
|
328
334
|
int size() const {
|
329
|
-
return len;
|
335
|
+
return len - offset;
|
330
336
|
}
|
331
337
|
|
332
338
|
Frame frame(int i) const {
|
@@ -336,7 +342,7 @@ struct RawSample {
|
|
336
342
|
return frame;
|
337
343
|
}
|
338
344
|
|
339
|
-
void sample() {
|
345
|
+
void sample(int offset = 0) {
|
340
346
|
clear();
|
341
347
|
|
342
348
|
if (!ruby_native_thread_p()) {
|
@@ -347,16 +353,18 @@ struct RawSample {
|
|
347
353
|
gc = true;
|
348
354
|
} else {
|
349
355
|
len = rb_profile_frames(0, MAX_LEN, frames, lines);
|
356
|
+
this->offset = std::min(offset, len);
|
350
357
|
}
|
351
358
|
}
|
352
359
|
|
353
360
|
void clear() {
|
354
361
|
len = 0;
|
362
|
+
offset = 0;
|
355
363
|
gc = false;
|
356
364
|
}
|
357
365
|
|
358
366
|
bool empty() const {
|
359
|
-
return len
|
367
|
+
return len <= offset;
|
360
368
|
}
|
361
369
|
};
|
362
370
|
|
@@ -366,7 +374,7 @@ struct RawSample {
|
|
366
374
|
struct LiveSample {
|
367
375
|
RawSample sample;
|
368
376
|
|
369
|
-
|
377
|
+
SignalSafeSemaphore sem_complete;
|
370
378
|
|
371
379
|
// Wait for a sample to be collected by the signal handler on another thread
|
372
380
|
void wait() {
|
@@ -393,41 +401,51 @@ struct LiveSample {
|
|
393
401
|
}
|
394
402
|
};
|
395
403
|
|
396
|
-
|
397
|
-
|
398
|
-
|
404
|
+
template <typename K>
|
405
|
+
class IndexMap {
|
406
|
+
public:
|
407
|
+
std::unordered_map<K, int> to_idx;
|
408
|
+
std::vector<K> list;
|
399
409
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
int idx = string_list.size();
|
404
|
-
string_list.push_back(str);
|
410
|
+
const K& operator[](int i) const noexcept {
|
411
|
+
return list[i];
|
412
|
+
}
|
405
413
|
|
406
|
-
|
407
|
-
|
414
|
+
size_t size() const noexcept {
|
415
|
+
return list.size();
|
408
416
|
}
|
409
417
|
|
410
|
-
|
411
|
-
|
418
|
+
int index(const K key) {
|
419
|
+
auto it = to_idx.find(key);
|
420
|
+
if (it == to_idx.end()) {
|
421
|
+
int idx = list.size();
|
422
|
+
list.push_back(key);
|
423
|
+
|
424
|
+
auto result = to_idx.insert({key, idx});
|
425
|
+
it = result.first;
|
426
|
+
}
|
427
|
+
|
428
|
+
return it->second;
|
429
|
+
}
|
430
|
+
|
431
|
+
void clear() {
|
432
|
+
list.clear();
|
433
|
+
to_idx.clear();
|
434
|
+
}
|
435
|
+
};
|
436
|
+
|
437
|
+
struct StackTable {
|
438
|
+
private:
|
412
439
|
|
413
440
|
struct FrameWithInfo {
|
414
441
|
Frame frame;
|
415
442
|
FrameInfo info;
|
416
443
|
};
|
417
444
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
auto it = frame_to_idx.find(frame);
|
423
|
-
if (it == frame_to_idx.end()) {
|
424
|
-
int idx = frame_list.size();
|
425
|
-
frame_list.push_back(frame);
|
426
|
-
auto result = frame_to_idx.insert({frame, idx});
|
427
|
-
it = result.first;
|
428
|
-
}
|
429
|
-
return it->second;
|
430
|
-
}
|
445
|
+
IndexMap<Frame> frame_map;
|
446
|
+
|
447
|
+
IndexMap<VALUE> func_map;
|
448
|
+
std::vector<FrameInfo> func_info_list;
|
431
449
|
|
432
450
|
struct StackNode {
|
433
451
|
std::unordered_map<Frame, int> children;
|
@@ -441,21 +459,13 @@ struct FrameList {
|
|
441
459
|
StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
|
442
460
|
};
|
443
461
|
|
462
|
+
// This mutex guards the StackNodes only. The rest of the maps and vectors
|
463
|
+
// should be guarded by the GVL
|
464
|
+
std::mutex stack_mutex;
|
465
|
+
|
444
466
|
StackNode root_stack_node;
|
445
467
|
vector<StackNode> stack_node_list;
|
446
|
-
|
447
|
-
int stack_index(const RawSample &stack) {
|
448
|
-
if (stack.empty()) {
|
449
|
-
throw std::runtime_error("VERNIER BUG: empty stack");
|
450
|
-
}
|
451
|
-
|
452
|
-
StackNode *node = &root_stack_node;
|
453
|
-
for (int i = 0; i < stack.size(); i++) {
|
454
|
-
Frame frame = stack.frame(i);
|
455
|
-
node = next_stack_node(node, frame);
|
456
|
-
}
|
457
|
-
return node->index;
|
458
|
-
}
|
468
|
+
int stack_node_list_finalized_idx = 0;
|
459
469
|
|
460
470
|
StackNode *next_stack_node(StackNode *node, Frame frame) {
|
461
471
|
auto search = node->children.find(frame);
|
@@ -475,83 +485,267 @@ struct FrameList {
|
|
475
485
|
}
|
476
486
|
}
|
477
487
|
|
488
|
+
public:
|
489
|
+
|
490
|
+
int stack_index(const RawSample &stack) {
|
491
|
+
if (stack.empty()) {
|
492
|
+
throw std::runtime_error("VERNIER BUG: empty stack");
|
493
|
+
}
|
494
|
+
|
495
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
496
|
+
|
497
|
+
StackNode *node = &root_stack_node;
|
498
|
+
for (int i = 0; i < stack.size(); i++) {
|
499
|
+
Frame frame = stack.frame(i);
|
500
|
+
node = next_stack_node(node, frame);
|
501
|
+
}
|
502
|
+
return node->index;
|
503
|
+
}
|
504
|
+
|
505
|
+
int stack_parent(int stack_idx) {
|
506
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
507
|
+
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
508
|
+
return -1;
|
509
|
+
} else {
|
510
|
+
return stack_node_list[stack_idx].parent;
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
int stack_frame(int stack_idx) {
|
515
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
516
|
+
if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
|
517
|
+
return -1;
|
518
|
+
} else {
|
519
|
+
return frame_map.index(stack_node_list[stack_idx].frame);
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
478
523
|
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
479
524
|
// which allocates.
|
480
525
|
void finalize() {
|
481
|
-
|
482
|
-
|
526
|
+
{
|
527
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
528
|
+
for (int i = stack_node_list_finalized_idx; i < stack_node_list.size(); i++) {
|
529
|
+
const auto &stack_node = stack_node_list[i];
|
530
|
+
frame_map.index(stack_node.frame);
|
531
|
+
func_map.index(stack_node.frame.frame);
|
532
|
+
stack_node_list_finalized_idx = i;
|
533
|
+
}
|
483
534
|
}
|
484
|
-
|
485
|
-
|
535
|
+
|
536
|
+
for (int i = func_info_list.size(); i < func_map.size(); i++) {
|
537
|
+
const auto &func = func_map[i];
|
538
|
+
// must not hold a mutex here
|
539
|
+
func_info_list.push_back(FrameInfo(func));
|
486
540
|
}
|
487
541
|
}
|
488
542
|
|
489
543
|
void mark_frames() {
|
544
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
545
|
+
|
490
546
|
for (auto stack_node: stack_node_list) {
|
491
547
|
rb_gc_mark(stack_node.frame.frame);
|
492
548
|
}
|
493
549
|
}
|
494
550
|
|
551
|
+
// FIXME: probably should remove
|
495
552
|
void clear() {
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
}
|
505
|
-
|
506
|
-
void write_result(VALUE result) {
|
507
|
-
FrameList &frame_list = *this;
|
508
|
-
|
509
|
-
VALUE stack_table = rb_hash_new();
|
510
|
-
rb_ivar_set(result, rb_intern("@stack_table"), stack_table);
|
511
|
-
VALUE stack_table_parent = rb_ary_new();
|
512
|
-
VALUE stack_table_frame = rb_ary_new();
|
513
|
-
rb_hash_aset(stack_table, sym("parent"), stack_table_parent);
|
514
|
-
rb_hash_aset(stack_table, sym("frame"), stack_table_frame);
|
515
|
-
for (const auto &stack : frame_list.stack_node_list) {
|
516
|
-
VALUE parent_val = stack.parent == -1 ? Qnil : INT2NUM(stack.parent);
|
517
|
-
rb_ary_push(stack_table_parent, parent_val);
|
518
|
-
rb_ary_push(stack_table_frame, INT2NUM(frame_list.frame_index(stack.frame)));
|
519
|
-
}
|
520
|
-
|
521
|
-
VALUE frame_table = rb_hash_new();
|
522
|
-
rb_ivar_set(result, rb_intern("@frame_table"), frame_table);
|
523
|
-
VALUE frame_table_func = rb_ary_new();
|
524
|
-
VALUE frame_table_line = rb_ary_new();
|
525
|
-
rb_hash_aset(frame_table, sym("func"), frame_table_func);
|
526
|
-
rb_hash_aset(frame_table, sym("line"), frame_table_line);
|
527
|
-
//for (const auto &frame : frame_list.frame_list) {
|
528
|
-
for (int i = 0; i < frame_list.frame_with_info_list.size(); i++) {
|
529
|
-
const auto &frame = frame_list.frame_with_info_list[i];
|
530
|
-
rb_ary_push(frame_table_func, INT2NUM(i));
|
531
|
-
rb_ary_push(frame_table_line, INT2NUM(frame.frame.line));
|
532
|
-
}
|
533
|
-
|
534
|
-
// TODO: dedup funcs before this step
|
535
|
-
VALUE func_table = rb_hash_new();
|
536
|
-
rb_ivar_set(result, rb_intern("@func_table"), func_table);
|
537
|
-
VALUE func_table_name = rb_ary_new();
|
538
|
-
VALUE func_table_filename = rb_ary_new();
|
539
|
-
VALUE func_table_first_line = rb_ary_new();
|
540
|
-
rb_hash_aset(func_table, sym("name"), func_table_name);
|
541
|
-
rb_hash_aset(func_table, sym("filename"), func_table_filename);
|
542
|
-
rb_hash_aset(func_table, sym("first_line"), func_table_first_line);
|
543
|
-
for (const auto &frame : frame_list.frame_with_info_list) {
|
544
|
-
const std::string label = frame.info.label;
|
545
|
-
const std::string filename = frame.info.file;
|
546
|
-
const int first_line = frame.info.first_lineno;
|
547
|
-
|
548
|
-
rb_ary_push(func_table_name, rb_str_new(label.c_str(), label.length()));
|
549
|
-
rb_ary_push(func_table_filename, rb_str_new(filename.c_str(), filename.length()));
|
550
|
-
rb_ary_push(func_table_first_line, INT2NUM(first_line));
|
553
|
+
frame_map.clear();
|
554
|
+
func_map.clear();
|
555
|
+
func_info_list.clear();
|
556
|
+
|
557
|
+
{
|
558
|
+
const std::lock_guard<std::mutex> lock(stack_mutex);
|
559
|
+
stack_node_list.clear();
|
560
|
+
root_stack_node.children.clear();
|
551
561
|
}
|
552
562
|
}
|
563
|
+
|
564
|
+
static VALUE stack_table_stack_count(VALUE self);
|
565
|
+
static VALUE stack_table_frame_count(VALUE self);
|
566
|
+
static VALUE stack_table_func_count(VALUE self);
|
567
|
+
|
568
|
+
static VALUE stack_table_frame_line_no(VALUE self, VALUE idxval);
|
569
|
+
static VALUE stack_table_frame_func_idx(VALUE self, VALUE idxval);
|
570
|
+
static VALUE stack_table_func_name(VALUE self, VALUE idxval);
|
571
|
+
static VALUE stack_table_func_filename(VALUE self, VALUE idxval);
|
572
|
+
static VALUE stack_table_func_first_lineno(VALUE self, VALUE idxval);
|
573
|
+
|
574
|
+
friend class SampleTranslator;
|
553
575
|
};
|
554
576
|
|
577
|
+
static void
|
578
|
+
stack_table_mark(void *data) {
|
579
|
+
StackTable *stack_table = static_cast<StackTable *>(data);
|
580
|
+
stack_table->mark_frames();
|
581
|
+
}
|
582
|
+
|
583
|
+
static void
|
584
|
+
stack_table_free(void *data) {
|
585
|
+
StackTable *stack_table = static_cast<StackTable *>(data);
|
586
|
+
delete stack_table;
|
587
|
+
}
|
588
|
+
|
589
|
+
static const rb_data_type_t rb_stack_table_type = {
|
590
|
+
.wrap_struct_name = "vernier/stack_table",
|
591
|
+
.function = {
|
592
|
+
//.dmemsize = rb_collector_memsize,
|
593
|
+
.dmark = stack_table_mark,
|
594
|
+
.dfree = stack_table_free,
|
595
|
+
},
|
596
|
+
};
|
597
|
+
|
598
|
+
static VALUE
|
599
|
+
stack_table_new(VALUE self) {
|
600
|
+
StackTable *stack_table = new StackTable();
|
601
|
+
VALUE obj = TypedData_Wrap_Struct(self, &rb_stack_table_type, stack_table);
|
602
|
+
return obj;
|
603
|
+
}
|
604
|
+
|
605
|
+
static StackTable *get_stack_table(VALUE obj) {
|
606
|
+
StackTable *stack_table;
|
607
|
+
TypedData_Get_Struct(obj, StackTable, &rb_stack_table_type, stack_table);
|
608
|
+
return stack_table;
|
609
|
+
}
|
610
|
+
|
611
|
+
static VALUE
|
612
|
+
stack_table_current_stack(int argc, VALUE *argv, VALUE self) {
|
613
|
+
int offset;
|
614
|
+
VALUE offset_v;
|
615
|
+
|
616
|
+
rb_scan_args(argc, argv, "01", &offset_v);
|
617
|
+
if (argc > 0) {
|
618
|
+
offset = NUM2INT(offset_v) + 1;
|
619
|
+
} else {
|
620
|
+
offset = 1;
|
621
|
+
}
|
622
|
+
|
623
|
+
StackTable *stack_table = get_stack_table(self);
|
624
|
+
RawSample stack;
|
625
|
+
stack.sample(offset);
|
626
|
+
int stack_index = stack_table->stack_index(stack);
|
627
|
+
return INT2NUM(stack_index);
|
628
|
+
}
|
629
|
+
|
630
|
+
static VALUE
|
631
|
+
stack_table_stack_parent_idx(VALUE self, VALUE idxval) {
|
632
|
+
StackTable *stack_table = get_stack_table(self);
|
633
|
+
int idx = NUM2INT(idxval);
|
634
|
+
int parent_idx = stack_table->stack_parent(idx);
|
635
|
+
if (parent_idx < 0) {
|
636
|
+
return Qnil;
|
637
|
+
} else {
|
638
|
+
return INT2NUM(parent_idx);
|
639
|
+
}
|
640
|
+
}
|
641
|
+
|
642
|
+
static VALUE
|
643
|
+
stack_table_stack_frame_idx(VALUE self, VALUE idxval) {
|
644
|
+
StackTable *stack_table = get_stack_table(self);
|
645
|
+
//stack_table->finalize();
|
646
|
+
int idx = NUM2INT(idxval);
|
647
|
+
int frame_idx = stack_table->stack_frame(idx);
|
648
|
+
return frame_idx < 0 ? Qnil : INT2NUM(frame_idx);
|
649
|
+
}
|
650
|
+
|
651
|
+
VALUE
|
652
|
+
StackTable::stack_table_stack_count(VALUE self) {
|
653
|
+
StackTable *stack_table = get_stack_table(self);
|
654
|
+
int count;
|
655
|
+
{
|
656
|
+
const std::lock_guard<std::mutex> lock(stack_table->stack_mutex);
|
657
|
+
count = stack_table->stack_node_list.size();
|
658
|
+
}
|
659
|
+
return INT2NUM(count);
|
660
|
+
}
|
661
|
+
|
662
|
+
VALUE
|
663
|
+
StackTable::stack_table_frame_count(VALUE self) {
|
664
|
+
StackTable *stack_table = get_stack_table(self);
|
665
|
+
stack_table->finalize();
|
666
|
+
int count = stack_table->frame_map.size();
|
667
|
+
return INT2NUM(count);
|
668
|
+
}
|
669
|
+
|
670
|
+
VALUE
|
671
|
+
StackTable::stack_table_func_count(VALUE self) {
|
672
|
+
StackTable *stack_table = get_stack_table(self);
|
673
|
+
stack_table->finalize();
|
674
|
+
int count = stack_table->func_map.size();
|
675
|
+
return INT2NUM(count);
|
676
|
+
}
|
677
|
+
|
678
|
+
VALUE
|
679
|
+
StackTable::stack_table_frame_line_no(VALUE self, VALUE idxval) {
|
680
|
+
StackTable *stack_table = get_stack_table(self);
|
681
|
+
stack_table->finalize();
|
682
|
+
int idx = NUM2INT(idxval);
|
683
|
+
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
684
|
+
return Qnil;
|
685
|
+
} else {
|
686
|
+
const auto &frame = stack_table->frame_map[idx];
|
687
|
+
return INT2NUM(frame.line);
|
688
|
+
}
|
689
|
+
}
|
690
|
+
|
691
|
+
VALUE
|
692
|
+
StackTable::stack_table_frame_func_idx(VALUE self, VALUE idxval) {
|
693
|
+
StackTable *stack_table = get_stack_table(self);
|
694
|
+
stack_table->finalize();
|
695
|
+
int idx = NUM2INT(idxval);
|
696
|
+
if (idx < 0 || idx >= stack_table->frame_map.size()) {
|
697
|
+
return Qnil;
|
698
|
+
} else {
|
699
|
+
const auto &frame = stack_table->frame_map[idx];
|
700
|
+
int func_idx = stack_table->func_map.index(frame.frame);
|
701
|
+
return INT2NUM(func_idx);
|
702
|
+
}
|
703
|
+
}
|
704
|
+
|
705
|
+
VALUE
|
706
|
+
StackTable::stack_table_func_name(VALUE self, VALUE idxval) {
|
707
|
+
StackTable *stack_table = get_stack_table(self);
|
708
|
+
stack_table->finalize();
|
709
|
+
int idx = NUM2INT(idxval);
|
710
|
+
auto &table = stack_table->func_info_list;
|
711
|
+
if (idx < 0 || idx >= table.size()) {
|
712
|
+
return Qnil;
|
713
|
+
} else {
|
714
|
+
const auto &func_info = table[idx];
|
715
|
+
const std::string &label = func_info.label;
|
716
|
+
return rb_interned_str(label.c_str(), label.length());
|
717
|
+
}
|
718
|
+
}
|
719
|
+
|
720
|
+
VALUE
|
721
|
+
StackTable::stack_table_func_filename(VALUE self, VALUE idxval) {
|
722
|
+
StackTable *stack_table = get_stack_table(self);
|
723
|
+
stack_table->finalize();
|
724
|
+
int idx = NUM2INT(idxval);
|
725
|
+
auto &table = stack_table->func_info_list;
|
726
|
+
if (idx < 0 || idx >= table.size()) {
|
727
|
+
return Qnil;
|
728
|
+
} else {
|
729
|
+
const auto &func_info = table[idx];
|
730
|
+
const std::string &filename = func_info.file;
|
731
|
+
return rb_interned_str(filename.c_str(), filename.length());
|
732
|
+
}
|
733
|
+
}
|
734
|
+
|
735
|
+
VALUE
|
736
|
+
StackTable::stack_table_func_first_lineno(VALUE self, VALUE idxval) {
|
737
|
+
StackTable *stack_table = get_stack_table(self);
|
738
|
+
stack_table->finalize();
|
739
|
+
int idx = NUM2INT(idxval);
|
740
|
+
auto &table = stack_table->func_info_list;
|
741
|
+
if (idx < 0 || idx >= table.size()) {
|
742
|
+
return Qnil;
|
743
|
+
} else {
|
744
|
+
const auto &func_info = table[idx];
|
745
|
+
return INT2NUM(func_info.first_lineno);
|
746
|
+
}
|
747
|
+
}
|
748
|
+
|
555
749
|
class SampleTranslator {
|
556
750
|
public:
|
557
751
|
int last_stack_index;
|
@@ -563,7 +757,7 @@ class SampleTranslator {
|
|
563
757
|
SampleTranslator() : len(0), last_stack_index(-1) {
|
564
758
|
}
|
565
759
|
|
566
|
-
int translate(
|
760
|
+
int translate(StackTable &frame_list, const RawSample &sample) {
|
567
761
|
int i = 0;
|
568
762
|
for (; i < len && i < sample.size(); i++) {
|
569
763
|
if (frames[i] != sample.frame(i)) {
|
@@ -571,7 +765,8 @@ class SampleTranslator {
|
|
571
765
|
}
|
572
766
|
}
|
573
767
|
|
574
|
-
|
768
|
+
const std::lock_guard<std::mutex> lock(frame_list.stack_mutex);
|
769
|
+
StackTable::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
|
575
770
|
|
576
771
|
for (; i < sample.size(); i++) {
|
577
772
|
Frame frame = sample.frame(i);
|
@@ -743,7 +938,6 @@ class SampleList {
|
|
743
938
|
|
744
939
|
std::vector<int> stacks;
|
745
940
|
std::vector<TimeStamp> timestamps;
|
746
|
-
std::vector<native_thread_id_t> threads;
|
747
941
|
std::vector<Category> categories;
|
748
942
|
std::vector<int> weights;
|
749
943
|
|
@@ -755,11 +949,10 @@ class SampleList {
|
|
755
949
|
return size() == 0;
|
756
950
|
}
|
757
951
|
|
758
|
-
void record_sample(int stack_index, TimeStamp time,
|
952
|
+
void record_sample(int stack_index, TimeStamp time, Category category) {
|
759
953
|
if (
|
760
954
|
!empty() &&
|
761
955
|
stacks.back() == stack_index &&
|
762
|
-
threads.back() == thread_id &&
|
763
956
|
categories.back() == category)
|
764
957
|
{
|
765
958
|
// We don't compare timestamps for de-duplication
|
@@ -767,7 +960,6 @@ class SampleList {
|
|
767
960
|
} else {
|
768
961
|
stacks.push_back(stack_index);
|
769
962
|
timestamps.push_back(time);
|
770
|
-
threads.push_back(thread_id);
|
771
963
|
categories.push_back(category);
|
772
964
|
weights.push_back(1);
|
773
965
|
}
|
@@ -841,7 +1033,7 @@ class Thread {
|
|
841
1033
|
}
|
842
1034
|
}
|
843
1035
|
|
844
|
-
void record_newobj(VALUE obj,
|
1036
|
+
void record_newobj(VALUE obj, StackTable &frame_list) {
|
845
1037
|
RawSample sample;
|
846
1038
|
sample.sample();
|
847
1039
|
|
@@ -931,12 +1123,12 @@ class Thread {
|
|
931
1123
|
|
932
1124
|
class ThreadTable {
|
933
1125
|
public:
|
934
|
-
|
1126
|
+
StackTable &frame_list;
|
935
1127
|
|
936
1128
|
std::vector<std::unique_ptr<Thread> > list;
|
937
1129
|
std::mutex mutex;
|
938
1130
|
|
939
|
-
ThreadTable(
|
1131
|
+
ThreadTable(StackTable &frame_list) : frame_list(frame_list) {
|
940
1132
|
}
|
941
1133
|
|
942
1134
|
void mark() {
|
@@ -1017,15 +1209,17 @@ class BaseCollector {
|
|
1017
1209
|
protected:
|
1018
1210
|
|
1019
1211
|
virtual void reset() {
|
1020
|
-
frame_list.clear();
|
1021
1212
|
}
|
1022
1213
|
|
1023
1214
|
public:
|
1024
1215
|
bool running = false;
|
1025
|
-
|
1216
|
+
StackTable *stack_table;
|
1217
|
+
VALUE stack_table_value;
|
1026
1218
|
|
1027
1219
|
TimeStamp started_at;
|
1028
1220
|
|
1221
|
+
BaseCollector(VALUE stack_table_value) : stack_table_value(stack_table_value), stack_table(get_stack_table(stack_table_value)) {
|
1222
|
+
}
|
1029
1223
|
virtual ~BaseCollector() {}
|
1030
1224
|
|
1031
1225
|
virtual bool start() {
|
@@ -1068,7 +1262,8 @@ class BaseCollector {
|
|
1068
1262
|
};
|
1069
1263
|
|
1070
1264
|
virtual void mark() {
|
1071
|
-
frame_list.mark_frames();
|
1265
|
+
//frame_list.mark_frames();
|
1266
|
+
rb_gc_mark(stack_table_value);
|
1072
1267
|
};
|
1073
1268
|
|
1074
1269
|
virtual VALUE get_markers() {
|
@@ -1082,16 +1277,15 @@ class CustomCollector : public BaseCollector {
|
|
1082
1277
|
void sample() {
|
1083
1278
|
RawSample sample;
|
1084
1279
|
sample.sample();
|
1085
|
-
int stack_index =
|
1280
|
+
int stack_index = stack_table->stack_index(sample);
|
1086
1281
|
|
1087
|
-
|
1088
|
-
samples.record_sample(stack_index, TimeStamp::Now(), thread_id, CATEGORY_NORMAL);
|
1282
|
+
samples.record_sample(stack_index, TimeStamp::Now(), CATEGORY_NORMAL);
|
1089
1283
|
}
|
1090
1284
|
|
1091
1285
|
VALUE stop() {
|
1092
1286
|
BaseCollector::stop();
|
1093
1287
|
|
1094
|
-
|
1288
|
+
stack_table->finalize();
|
1095
1289
|
|
1096
1290
|
VALUE result = build_collector_result();
|
1097
1291
|
|
@@ -1112,10 +1306,12 @@ class CustomCollector : public BaseCollector {
|
|
1112
1306
|
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
1113
1307
|
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
1114
1308
|
|
1115
|
-
frame_list.write_result(result);
|
1116
|
-
|
1117
1309
|
return result;
|
1118
1310
|
}
|
1311
|
+
|
1312
|
+
public:
|
1313
|
+
|
1314
|
+
CustomCollector(VALUE stack_table) : BaseCollector(stack_table) { }
|
1119
1315
|
};
|
1120
1316
|
|
1121
1317
|
class RetainedCollector : public BaseCollector {
|
@@ -1135,7 +1331,7 @@ class RetainedCollector : public BaseCollector {
|
|
1135
1331
|
// Ideally we'd allow empty samples to be represented
|
1136
1332
|
return;
|
1137
1333
|
}
|
1138
|
-
int stack_index =
|
1334
|
+
int stack_index = stack_table->stack_index(sample);
|
1139
1335
|
|
1140
1336
|
object_list.push_back(obj);
|
1141
1337
|
object_frames.emplace(obj, stack_index);
|
@@ -1165,6 +1361,8 @@ class RetainedCollector : public BaseCollector {
|
|
1165
1361
|
|
1166
1362
|
public:
|
1167
1363
|
|
1364
|
+
RetainedCollector(VALUE stack_table) : BaseCollector(stack_table) { }
|
1365
|
+
|
1168
1366
|
bool start() {
|
1169
1367
|
if (!BaseCollector::start()) {
|
1170
1368
|
return false;
|
@@ -1191,7 +1389,7 @@ class RetainedCollector : public BaseCollector {
|
|
1191
1389
|
rb_tracepoint_disable(tp_newobj);
|
1192
1390
|
tp_newobj = Qnil;
|
1193
1391
|
|
1194
|
-
|
1392
|
+
stack_table->finalize();
|
1195
1393
|
|
1196
1394
|
// We should have collected info for all our frames, so no need to continue
|
1197
1395
|
// marking them
|
@@ -1213,7 +1411,7 @@ class RetainedCollector : public BaseCollector {
|
|
1213
1411
|
|
1214
1412
|
VALUE build_collector_result() {
|
1215
1413
|
RetainedCollector *collector = this;
|
1216
|
-
|
1414
|
+
StackTable &frame_list = *collector->stack_table;
|
1217
1415
|
|
1218
1416
|
VALUE result = BaseCollector::build_collector_result();
|
1219
1417
|
|
@@ -1241,8 +1439,6 @@ class RetainedCollector : public BaseCollector {
|
|
1241
1439
|
}
|
1242
1440
|
}
|
1243
1441
|
|
1244
|
-
frame_list.write_result(result);
|
1245
|
-
|
1246
1442
|
return result;
|
1247
1443
|
}
|
1248
1444
|
|
@@ -1251,7 +1447,8 @@ class RetainedCollector : public BaseCollector {
|
|
1251
1447
|
// can be garbage collected.
|
1252
1448
|
// When we stop collection we will stringify the remaining frames, and then
|
1253
1449
|
// clear them from the set, allowing them to be removed from out output.
|
1254
|
-
|
1450
|
+
stack_table->mark_frames();
|
1451
|
+
rb_gc_mark(stack_table_value);
|
1255
1452
|
|
1256
1453
|
rb_gc_mark(tp_newobj);
|
1257
1454
|
rb_gc_mark(tp_freeobj);
|
@@ -1333,7 +1530,7 @@ class TimeCollector : public BaseCollector {
|
|
1333
1530
|
pthread_t sample_thread;
|
1334
1531
|
|
1335
1532
|
atomic_bool running;
|
1336
|
-
|
1533
|
+
SignalSafeSemaphore thread_stopped;
|
1337
1534
|
|
1338
1535
|
TimeStamp interval;
|
1339
1536
|
unsigned int allocation_sample_rate;
|
@@ -1350,7 +1547,7 @@ class TimeCollector : public BaseCollector {
|
|
1350
1547
|
}
|
1351
1548
|
|
1352
1549
|
public:
|
1353
|
-
TimeCollector(TimeStamp interval, unsigned int allocation_sample_rate) : interval(interval), allocation_sample_rate(allocation_sample_rate), threads(
|
1550
|
+
TimeCollector(VALUE stack_table, TimeStamp interval, unsigned int allocation_sample_rate) : BaseCollector(stack_table), interval(interval), allocation_sample_rate(allocation_sample_rate), threads(*get_stack_table(stack_table)) {
|
1354
1551
|
}
|
1355
1552
|
|
1356
1553
|
void record_newobj(VALUE obj) {
|
@@ -1376,11 +1573,10 @@ class TimeCollector : public BaseCollector {
|
|
1376
1573
|
|
1377
1574
|
void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
|
1378
1575
|
if (!sample.empty()) {
|
1379
|
-
int stack_index = thread.translator.translate(
|
1576
|
+
int stack_index = thread.translator.translate(*stack_table, sample);
|
1380
1577
|
thread.samples.record_sample(
|
1381
1578
|
stack_index,
|
1382
1579
|
time,
|
1383
|
-
thread.native_tid,
|
1384
1580
|
category
|
1385
1581
|
);
|
1386
1582
|
}
|
@@ -1430,7 +1626,7 @@ class TimeCollector : public BaseCollector {
|
|
1430
1626
|
// that by the GVL instrumentation, but let's try to get
|
1431
1627
|
// it to a consistent state and stop profiling it.
|
1432
1628
|
thread.set_state(Thread::State::STOPPED);
|
1433
|
-
} else if (sample.sample.
|
1629
|
+
} else if (sample.sample.empty()) {
|
1434
1630
|
// fprintf(stderr, "skipping GC sample\n");
|
1435
1631
|
} else {
|
1436
1632
|
record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
|
@@ -1439,7 +1635,6 @@ class TimeCollector : public BaseCollector {
|
|
1439
1635
|
thread.samples.record_sample(
|
1440
1636
|
thread.stack_on_suspend_idx,
|
1441
1637
|
sample_start,
|
1442
|
-
thread.native_tid,
|
1443
1638
|
CATEGORY_IDLE);
|
1444
1639
|
} else {
|
1445
1640
|
}
|
@@ -1602,7 +1797,7 @@ class TimeCollector : public BaseCollector {
|
|
1602
1797
|
rb_remove_event_hook(internal_gc_event_cb);
|
1603
1798
|
rb_remove_event_hook(internal_thread_event_cb);
|
1604
1799
|
|
1605
|
-
|
1800
|
+
stack_table->finalize();
|
1606
1801
|
|
1607
1802
|
VALUE result = build_collector_result();
|
1608
1803
|
|
@@ -1632,13 +1827,12 @@ class TimeCollector : public BaseCollector {
|
|
1632
1827
|
|
1633
1828
|
}
|
1634
1829
|
|
1635
|
-
frame_list.write_result(result);
|
1636
|
-
|
1637
1830
|
return result;
|
1638
1831
|
}
|
1639
1832
|
|
1640
1833
|
void mark() {
|
1641
|
-
|
1834
|
+
stack_table->mark_frames();
|
1835
|
+
rb_gc_mark(stack_table_value);
|
1642
1836
|
threads.mark();
|
1643
1837
|
|
1644
1838
|
//for (int i = 0; i < queued_length; i++) {
|
@@ -1710,12 +1904,21 @@ collector_sample(VALUE self) {
|
|
1710
1904
|
return Qtrue;
|
1711
1905
|
}
|
1712
1906
|
|
1907
|
+
static VALUE
|
1908
|
+
collector_stack_table(VALUE self) {
|
1909
|
+
auto *collector = get_collector(self);
|
1910
|
+
|
1911
|
+
return collector->stack_table_value;
|
1912
|
+
}
|
1913
|
+
|
1713
1914
|
static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
1714
1915
|
BaseCollector *collector;
|
1916
|
+
|
1917
|
+
VALUE stack_table = stack_table_new(rb_cStackTable);
|
1715
1918
|
if (mode == sym("retained")) {
|
1716
|
-
collector = new RetainedCollector();
|
1919
|
+
collector = new RetainedCollector(stack_table);
|
1717
1920
|
} else if (mode == sym("custom")) {
|
1718
|
-
collector = new CustomCollector();
|
1921
|
+
collector = new CustomCollector(stack_table);
|
1719
1922
|
} else if (mode == sym("wall")) {
|
1720
1923
|
VALUE intervalv = rb_hash_aref(options, sym("interval"));
|
1721
1924
|
TimeStamp interval;
|
@@ -1732,7 +1935,7 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
1732
1935
|
} else {
|
1733
1936
|
allocation_sample_rate = NUM2UINT(allocation_sample_ratev);
|
1734
1937
|
}
|
1735
|
-
collector = new TimeCollector(interval, allocation_sample_rate);
|
1938
|
+
collector = new TimeCollector(stack_table, interval, allocation_sample_rate);
|
1736
1939
|
} else {
|
1737
1940
|
rb_raise(rb_eArgError, "invalid mode");
|
1738
1941
|
}
|
@@ -1786,9 +1989,25 @@ Init_vernier(void)
|
|
1786
1989
|
rb_define_singleton_method(rb_cVernierCollector, "_new", collector_new, 2);
|
1787
1990
|
rb_define_method(rb_cVernierCollector, "start", collector_start, 0);
|
1788
1991
|
rb_define_method(rb_cVernierCollector, "sample", collector_sample, 0);
|
1992
|
+
rb_define_method(rb_cVernierCollector, "stack_table", collector_stack_table, 0);
|
1789
1993
|
rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
|
1790
1994
|
rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
|
1791
1995
|
|
1996
|
+
rb_cStackTable = rb_define_class_under(rb_mVernier, "StackTable", rb_cObject);
|
1997
|
+
rb_undef_alloc_func(rb_cStackTable);
|
1998
|
+
rb_define_singleton_method(rb_cStackTable, "new", stack_table_new, 0);
|
1999
|
+
rb_define_method(rb_cStackTable, "current_stack", stack_table_current_stack, -1);
|
2000
|
+
rb_define_method(rb_cStackTable, "stack_parent_idx", stack_table_stack_parent_idx, 1);
|
2001
|
+
rb_define_method(rb_cStackTable, "stack_frame_idx", stack_table_stack_frame_idx, 1);
|
2002
|
+
rb_define_method(rb_cStackTable, "frame_line_no", StackTable::stack_table_frame_line_no, 1);
|
2003
|
+
rb_define_method(rb_cStackTable, "frame_func_idx", StackTable::stack_table_frame_func_idx, 1);
|
2004
|
+
rb_define_method(rb_cStackTable, "func_name", StackTable::stack_table_func_name, 1);
|
2005
|
+
rb_define_method(rb_cStackTable, "func_filename", StackTable::stack_table_func_filename, 1);
|
2006
|
+
rb_define_method(rb_cStackTable, "func_first_lineno", StackTable::stack_table_func_first_lineno, 1);
|
2007
|
+
rb_define_method(rb_cStackTable, "stack_count", StackTable::stack_table_stack_count, 0);
|
2008
|
+
rb_define_method(rb_cStackTable, "frame_count", StackTable::stack_table_frame_count, 0);
|
2009
|
+
rb_define_method(rb_cStackTable, "func_count", StackTable::stack_table_func_count, 0);
|
2010
|
+
|
1792
2011
|
Init_consts(rb_mVernierMarkerPhase);
|
1793
2012
|
|
1794
2013
|
//static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
|