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.
@@ -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 SamplerSemaphore {
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
- SamplerSemaphore(unsigned int value = 0) {
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
- ~SamplerSemaphore() {
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
- struct RawSample {
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
- RawSample() : len(0), gc(false) { }
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 == 0;
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
- SamplerSemaphore sem_complete;
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
- struct FrameList {
397
- std::unordered_map<std::string, int> string_to_idx;
398
- std::vector<std::string> string_list;
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
- int string_index(const std::string str) {
401
- auto it = string_to_idx.find(str);
402
- if (it == string_to_idx.end()) {
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
- auto result = string_to_idx.insert({str, idx});
407
- it = result.first;
414
+ size_t size() const noexcept {
415
+ return list.size();
408
416
  }
409
417
 
410
- return it->second;
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
- std::unordered_map<Frame, int> frame_to_idx;
419
- std::vector<Frame> frame_list;
420
- std::vector<FrameWithInfo> frame_with_info_list;
421
- int frame_index(const Frame frame) {
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
- for (const auto &stack_node : stack_node_list) {
482
- frame_index(stack_node.frame);
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
- for (const auto &frame : frame_list) {
485
- frame_with_info_list.push_back(FrameWithInfo{frame, frame.info()});
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
- string_list.clear();
497
- frame_list.clear();
498
- stack_node_list.clear();
499
- frame_with_info_list.clear();
500
-
501
- string_to_idx.clear();
502
- frame_to_idx.clear();
503
- root_stack_node.children.clear();
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(FrameList &frame_list, const RawSample &sample) {
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
- FrameList::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
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, native_thread_id_t thread_id, Category category) {
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, FrameList &frame_list) {
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
- FrameList &frame_list;
1126
+ StackTable &frame_list;
935
1127
 
936
1128
  std::vector<std::unique_ptr<Thread> > list;
937
1129
  std::mutex mutex;
938
1130
 
939
- ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
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
- FrameList frame_list;
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 = frame_list.stack_index(sample);
1280
+ int stack_index = stack_table->stack_index(sample);
1086
1281
 
1087
- native_thread_id_t thread_id = 0;
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
- frame_list.finalize();
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 = frame_list.stack_index(sample);
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
- frame_list.finalize();
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
- FrameList &frame_list = collector->frame_list;
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
- frame_list.mark_frames();
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
- SamplerSemaphore thread_stopped;
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(frame_list) {
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(frame_list, sample);
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.gc) {
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
- frame_list.finalize();
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
- frame_list.mark_frames();
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);