vernier 0.2.1 → 0.3.1

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.
@@ -27,14 +27,20 @@
27
27
  #include "ruby/debug.h"
28
28
  #include "ruby/thread.h"
29
29
 
30
- // GC event's we'll monitor during profiling
31
- #define RUBY_GC_PHASE_EVENTS \
30
+ # define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
31
+
32
+ // Internal TracePoint events we'll monitor during profiling
33
+ #define RUBY_INTERNAL_EVENTS \
32
34
  RUBY_INTERNAL_EVENT_GC_START | \
33
35
  RUBY_INTERNAL_EVENT_GC_END_MARK | \
34
36
  RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
35
37
  RUBY_INTERNAL_EVENT_GC_ENTER | \
36
38
  RUBY_INTERNAL_EVENT_GC_EXIT
37
39
 
40
+ #define RUBY_NORMAL_EVENTS \
41
+ RUBY_EVENT_THREAD_BEGIN | \
42
+ RUBY_EVENT_THREAD_END
43
+
38
44
  #define sym(name) ID2SYM(rb_intern_const(name))
39
45
 
40
46
  // HACK: This isn't public, but the objspace ext uses it
@@ -66,13 +72,17 @@ class TimeStamp {
66
72
  return TimeStamp(0);
67
73
  }
68
74
 
69
- static void Sleep(const TimeStamp &time) {
70
- struct timespec ts = time.timespec();
75
+ // SleepUntil a specified timestamp
76
+ // Highly accurate manual sleep time
77
+ static void SleepUntil(const TimeStamp &target_time) {
78
+ if (target_time.zero()) return;
79
+ struct timespec ts = target_time.timespec();
71
80
 
72
81
  int res;
73
82
  do {
74
- res = nanosleep(&ts, &ts);
75
- } while (res && errno == EINTR);
83
+ // do nothing until it's time :)
84
+ sleep(0);
85
+ } while (target_time > TimeStamp::Now());
76
86
  }
77
87
 
78
88
  static TimeStamp from_microseconds(uint64_t us) {
@@ -125,6 +135,14 @@ class TimeStamp {
125
135
  return value_ns >= other.value_ns;
126
136
  }
127
137
 
138
+ bool operator==(const TimeStamp &other) const {
139
+ return value_ns == other.value_ns;
140
+ }
141
+
142
+ bool operator!=(const TimeStamp &other) const {
143
+ return value_ns != other.value_ns;
144
+ }
145
+
128
146
  uint64_t nanoseconds() const {
129
147
  return value_ns;
130
148
  }
@@ -202,6 +220,10 @@ bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
202
220
  return lhs.frame == rhs.frame && lhs.line == rhs.line;
203
221
  }
204
222
 
223
+ bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
224
+ return !(lhs == rhs);
225
+ }
226
+
205
227
  namespace std {
206
228
  template<>
207
229
  struct hash<Frame>
@@ -275,7 +297,9 @@ struct RawSample {
275
297
  }
276
298
 
277
299
  Frame frame(int i) const {
278
- const Frame frame = {frames[i], lines[i]};
300
+ int idx = len - i - 1;
301
+ if (idx < 0) throw std::out_of_range("out of range");
302
+ const Frame frame = {frames[idx], lines[idx]};
279
303
  return frame;
280
304
  }
281
305
 
@@ -337,24 +361,6 @@ struct LiveSample {
337
361
  }
338
362
  };
339
363
 
340
- struct TraceArg {
341
- rb_trace_arg_t *tparg;
342
- VALUE obj;
343
- VALUE path;
344
- VALUE line;
345
- VALUE mid;
346
- VALUE klass;
347
-
348
- TraceArg(VALUE tpval) {
349
- tparg = rb_tracearg_from_tracepoint(tpval);
350
- obj = rb_tracearg_object(tparg);
351
- path = rb_tracearg_path(tparg);
352
- line = rb_tracearg_lineno(tparg);
353
- mid = rb_tracearg_method_id(tparg);
354
- klass = rb_tracearg_defined_class(tparg);
355
- }
356
- };
357
-
358
364
  struct FrameList {
359
365
  std::unordered_map<std::string, int> string_to_idx;
360
366
  std::vector<std::string> string_list;
@@ -412,27 +418,29 @@ struct FrameList {
412
418
  }
413
419
 
414
420
  StackNode *node = &root_stack_node;
415
- for (int i = stack.size() - 1; i >= 0; i--) {
416
- const Frame &frame = stack.frame(i);
421
+ for (int i = 0; i < stack.size(); i++) {
422
+ Frame frame = stack.frame(i);
417
423
  node = next_stack_node(node, frame);
418
424
  }
419
425
  return node->index;
420
426
  }
421
427
 
422
- StackNode *next_stack_node(StackNode *node, const Frame &frame) {
423
- int next_node_idx = node->children[frame];
424
- if (next_node_idx == 0) {
428
+ StackNode *next_stack_node(StackNode *node, Frame frame) {
429
+ auto search = node->children.find(frame);
430
+ if (search == node->children.end()) {
425
431
  // insert a new node
426
- next_node_idx = stack_node_list.size();
432
+ int next_node_idx = stack_node_list.size();
427
433
  node->children[frame] = next_node_idx;
428
434
  stack_node_list.emplace_back(
429
435
  frame,
430
436
  next_node_idx,
431
437
  node->index
432
438
  );
439
+ return &stack_node_list[next_node_idx];
440
+ } else {
441
+ int node_idx = search->second;
442
+ return &stack_node_list[node_idx];
433
443
  }
434
-
435
- return &stack_node_list[next_node_idx];
436
444
  }
437
445
 
438
446
  // Converts Frames from stacks other tables. "Symbolicates" the frames
@@ -512,6 +520,372 @@ struct FrameList {
512
520
  }
513
521
  };
514
522
 
523
+ class SampleTranslator {
524
+ public:
525
+ int last_stack_index;
526
+
527
+ Frame frames[RawSample::MAX_LEN];
528
+ int frame_indexes[RawSample::MAX_LEN];
529
+ int len;
530
+
531
+ SampleTranslator() : len(0), last_stack_index(-1) {
532
+ }
533
+
534
+ int translate(FrameList &frame_list, const RawSample &sample) {
535
+ int i = 0;
536
+ for (; i < len && i < sample.size(); i++) {
537
+ if (frames[i] != sample.frame(i)) {
538
+ break;
539
+ }
540
+ }
541
+
542
+ FrameList::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
543
+
544
+ for (; i < sample.size(); i++) {
545
+ Frame frame = sample.frame(i);
546
+ node = frame_list.next_stack_node(node, frame);
547
+
548
+ frames[i] = frame;
549
+ frame_indexes[i] = node->index;
550
+ }
551
+ len = i;
552
+
553
+ last_stack_index = node->index;
554
+ return last_stack_index;
555
+ }
556
+ };
557
+
558
+ typedef uint64_t native_thread_id_t;
559
+ static native_thread_id_t get_native_thread_id() {
560
+ #ifdef __APPLE__
561
+ uint64_t thread_id;
562
+ int e = pthread_threadid_np(pthread_self(), &thread_id);
563
+ if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
564
+ return thread_id;
565
+ #else
566
+ // gettid() is only available as of glibc 2.30
567
+ pid_t tid = syscall(SYS_gettid);
568
+ return tid;
569
+ #endif
570
+ }
571
+
572
+
573
+ class Marker {
574
+ public:
575
+ enum Type {
576
+ MARKER_GVL_THREAD_STARTED,
577
+ MARKER_GVL_THREAD_EXITED,
578
+
579
+ MARKER_GC_START,
580
+ MARKER_GC_END_MARK,
581
+ MARKER_GC_END_SWEEP,
582
+ MARKER_GC_ENTER,
583
+ MARKER_GC_EXIT,
584
+ MARKER_GC_PAUSE,
585
+
586
+ MARKER_THREAD_RUNNING,
587
+ MARKER_THREAD_STALLED,
588
+ MARKER_THREAD_SUSPENDED,
589
+
590
+ MARKER_MAX,
591
+ };
592
+
593
+ // Must match phase types from Gecko
594
+ enum Phase {
595
+ INSTANT,
596
+ INTERVAL,
597
+ INTERVAL_START,
598
+ INTERVAL_END
599
+ };
600
+
601
+ Type type;
602
+ Phase phase;
603
+ TimeStamp timestamp;
604
+ TimeStamp finish;
605
+ native_thread_id_t thread_id;
606
+ int stack_index = -1;
607
+
608
+ VALUE to_array() {
609
+ VALUE record[6] = {0};
610
+ record[0] = ULL2NUM(thread_id);
611
+ record[1] = INT2NUM(type);
612
+ record[2] = INT2NUM(phase);
613
+ record[3] = ULL2NUM(timestamp.nanoseconds());
614
+
615
+ if (phase == Marker::Phase::INTERVAL) {
616
+ record[4] = ULL2NUM(finish.nanoseconds());
617
+ }
618
+ else {
619
+ record[4] = Qnil;
620
+ }
621
+ record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
622
+
623
+ return rb_ary_new_from_values(6, record);
624
+ }
625
+ };
626
+
627
+ class MarkerTable {
628
+ TimeStamp last_gc_entry;
629
+
630
+ public:
631
+ std::vector<Marker> list;
632
+ std::mutex mutex;
633
+
634
+ void record_gc_entered() {
635
+ last_gc_entry = TimeStamp::Now();
636
+ }
637
+
638
+ void record_gc_leave() {
639
+ list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), get_native_thread_id(), -1 });
640
+ }
641
+
642
+ void record_interval(Marker::Type type, TimeStamp from, TimeStamp to, int stack_index = -1) {
643
+ const std::lock_guard<std::mutex> lock(mutex);
644
+
645
+ list.push_back({ type, Marker::INTERVAL, from, to, get_native_thread_id(), stack_index });
646
+ }
647
+
648
+ void record(Marker::Type type, int stack_index = -1) {
649
+ const std::lock_guard<std::mutex> lock(mutex);
650
+
651
+ list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), get_native_thread_id(), stack_index });
652
+ }
653
+ };
654
+
655
+ enum Category{
656
+ CATEGORY_NORMAL,
657
+ CATEGORY_IDLE
658
+ };
659
+
660
+ class SampleList {
661
+ public:
662
+
663
+ std::vector<int> stacks;
664
+ std::vector<TimeStamp> timestamps;
665
+ std::vector<native_thread_id_t> threads;
666
+ std::vector<Category> categories;
667
+ std::vector<int> weights;
668
+
669
+ size_t size() {
670
+ return stacks.size();
671
+ }
672
+
673
+ bool empty() {
674
+ return size() == 0;
675
+ }
676
+
677
+ void record_sample(int stack_index, TimeStamp time, native_thread_id_t thread_id, Category category) {
678
+ if (
679
+ !empty() &&
680
+ stacks.back() == stack_index &&
681
+ threads.back() == thread_id &&
682
+ categories.back() == category)
683
+ {
684
+ // We don't compare timestamps for de-duplication
685
+ weights.back() += 1;
686
+ } else {
687
+ stacks.push_back(stack_index);
688
+ timestamps.push_back(time);
689
+ threads.push_back(thread_id);
690
+ categories.push_back(category);
691
+ weights.push_back(1);
692
+ }
693
+ }
694
+
695
+ void write_result(VALUE result) const {
696
+ VALUE samples = rb_ary_new();
697
+ rb_hash_aset(result, sym("samples"), samples);
698
+ for (auto& stack_index: this->stacks) {
699
+ rb_ary_push(samples, INT2NUM(stack_index));
700
+ }
701
+
702
+ VALUE weights = rb_ary_new();
703
+ rb_hash_aset(result, sym("weights"), weights);
704
+ for (auto& weight: this->weights) {
705
+ rb_ary_push(weights, INT2NUM(weight));
706
+ }
707
+
708
+ VALUE timestamps = rb_ary_new();
709
+ rb_hash_aset(result, sym("timestamps"), timestamps);
710
+ for (auto& timestamp: this->timestamps) {
711
+ rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
712
+ }
713
+
714
+ VALUE sample_categories = rb_ary_new();
715
+ rb_hash_aset(result, sym("sample_categories"), sample_categories);
716
+ for (auto& cat: this->categories) {
717
+ rb_ary_push(sample_categories, INT2NUM(cat));
718
+ }
719
+ }
720
+ };
721
+
722
+ class Thread {
723
+ public:
724
+ SampleList samples;
725
+
726
+ enum State {
727
+ STARTED,
728
+ RUNNING,
729
+ READY,
730
+ SUSPENDED,
731
+ STOPPED
732
+ };
733
+
734
+ pthread_t pthread_id;
735
+ native_thread_id_t native_tid;
736
+ State state;
737
+
738
+ TimeStamp state_changed_at;
739
+ TimeStamp started_at;
740
+ TimeStamp stopped_at;
741
+
742
+ int stack_on_suspend_idx;
743
+ SampleTranslator translator;
744
+
745
+ std::string name;
746
+
747
+ Thread(State state) : state(state), stack_on_suspend_idx(-1) {
748
+ pthread_id = pthread_self();
749
+ native_tid = get_native_thread_id();
750
+ started_at = state_changed_at = TimeStamp::Now();
751
+ }
752
+
753
+ void set_state(State new_state, MarkerTable *markers) {
754
+ if (state == Thread::State::STOPPED) {
755
+ return;
756
+ }
757
+
758
+ TimeStamp from = state_changed_at;
759
+ auto now = TimeStamp::Now();
760
+
761
+ if (started_at.zero()) {
762
+ started_at = now;
763
+ }
764
+
765
+ switch (new_state) {
766
+ case State::STARTED:
767
+ new_state = State::RUNNING;
768
+ break;
769
+ case State::RUNNING:
770
+ assert(state == State::READY);
771
+
772
+ // If the GVL is immediately ready, and we measure no times
773
+ // stalled, skip emitting the interval.
774
+ if (from != now) {
775
+ markers->record_interval(Marker::Type::MARKER_THREAD_STALLED, from, now);
776
+ }
777
+ break;
778
+ case State::READY:
779
+ // The ready state means "I would like to do some work, but I can't
780
+ // do it right now either because I blocked on IO and now I want the GVL back,
781
+ // or because the VM timer put me to sleep"
782
+ //
783
+ // Threads can be preempted, which means they will have been in "Running"
784
+ // state, and then the VM was like "no I need to stop you from working,
785
+ // so I'll put you in the 'ready' (or stalled) state"
786
+ assert(state == State::SUSPENDED || state == State::RUNNING);
787
+ if (state == State::SUSPENDED) {
788
+ markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
789
+ }
790
+ else {
791
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
792
+ }
793
+ break;
794
+ case State::SUSPENDED:
795
+ // We can go from RUNNING or STARTED to SUSPENDED
796
+ assert(state == State::RUNNING || state == State::STARTED);
797
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
798
+ break;
799
+ case State::STOPPED:
800
+ // We can go from RUNNING or STARTED to STOPPED
801
+ assert(state == State::RUNNING || state == State::STARTED);
802
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
803
+ stopped_at = now;
804
+ capture_name();
805
+
806
+ break;
807
+ }
808
+
809
+ state = new_state;
810
+ state_changed_at = now;
811
+ }
812
+
813
+ bool running() {
814
+ return state != State::STOPPED;
815
+ }
816
+
817
+ void capture_name() {
818
+ char buf[128];
819
+ int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
820
+ if (rc == 0)
821
+ name = std::string(buf);
822
+ }
823
+ };
824
+
825
+ class ThreadTable {
826
+ public:
827
+ FrameList &frame_list;
828
+
829
+ std::vector<Thread> list;
830
+ std::mutex mutex;
831
+
832
+ ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
833
+ }
834
+
835
+ void started(MarkerTable *markers) {
836
+ //const std::lock_guard<std::mutex> lock(mutex);
837
+
838
+ //list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
839
+ markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
840
+ set_state(Thread::State::STARTED, markers);
841
+ }
842
+
843
+ void ready(MarkerTable *markers) {
844
+ set_state(Thread::State::READY, markers);
845
+ }
846
+
847
+ void resumed(MarkerTable *markers) {
848
+ set_state(Thread::State::RUNNING, markers);
849
+ }
850
+
851
+ void suspended(MarkerTable *markers) {
852
+ set_state(Thread::State::SUSPENDED, markers);
853
+ }
854
+
855
+ void stopped(MarkerTable *markers) {
856
+ markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
857
+ set_state(Thread::State::STOPPED, markers);
858
+ }
859
+
860
+ private:
861
+ void set_state(Thread::State new_state, MarkerTable *markers) {
862
+ const std::lock_guard<std::mutex> lock(mutex);
863
+
864
+ pthread_t current_thread = pthread_self();
865
+ //cerr << "set state=" << new_state << " thread=" << gettid() << endl;
866
+
867
+ for (auto &thread : list) {
868
+ if (pthread_equal(current_thread, thread.pthread_id)) {
869
+ if (new_state == Thread::State::SUSPENDED) {
870
+
871
+ RawSample sample;
872
+ sample.sample();
873
+
874
+ thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
875
+ //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
876
+ }
877
+
878
+ thread.set_state(new_state, markers);
879
+
880
+ return;
881
+ }
882
+ }
883
+
884
+ pid_t native_tid = get_native_thread_id();
885
+ list.emplace_back(new_state);
886
+ }
887
+ };
888
+
515
889
  class BaseCollector {
516
890
  protected:
517
891
 
@@ -523,15 +897,19 @@ class BaseCollector {
523
897
  bool running = false;
524
898
  FrameList frame_list;
525
899
 
900
+ TimeStamp started_at;
901
+
526
902
  virtual ~BaseCollector() {}
527
903
 
528
904
  virtual bool start() {
529
905
  if (running) {
530
906
  return false;
531
- } else {
532
- running = true;
533
- return true;
534
907
  }
908
+
909
+ started_at = TimeStamp::Now();
910
+
911
+ running = true;
912
+ return true;
535
913
  }
536
914
 
537
915
  virtual VALUE stop() {
@@ -543,6 +921,21 @@ class BaseCollector {
543
921
  return Qnil;
544
922
  }
545
923
 
924
+ void write_meta(VALUE result) {
925
+ VALUE meta = rb_hash_new();
926
+ rb_ivar_set(result, rb_intern("@meta"), meta);
927
+ rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
928
+
929
+ }
930
+
931
+ virtual VALUE build_collector_result() {
932
+ VALUE result = rb_obj_alloc(rb_cVernierResult);
933
+
934
+ write_meta(result);
935
+
936
+ return result;
937
+ }
938
+
546
939
  virtual void sample() {
547
940
  rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
548
941
  };
@@ -557,14 +950,15 @@ class BaseCollector {
557
950
  };
558
951
 
559
952
  class CustomCollector : public BaseCollector {
560
- std::vector<int> samples;
953
+ SampleList samples;
561
954
 
562
955
  void sample() {
563
956
  RawSample sample;
564
957
  sample.sample();
565
958
  int stack_index = frame_list.stack_index(sample);
566
959
 
567
- samples.push_back(stack_index);
960
+ native_thread_id_t thread_id = 0;
961
+ samples.record_sample(stack_index, TimeStamp::Now(), thread_id, CATEGORY_NORMAL);
568
962
  }
569
963
 
570
964
  VALUE stop() {
@@ -580,17 +974,16 @@ class CustomCollector : public BaseCollector {
580
974
  }
581
975
 
582
976
  VALUE build_collector_result() {
583
- VALUE result = rb_obj_alloc(rb_cVernierResult);
977
+ VALUE result = BaseCollector::build_collector_result();
584
978
 
585
- VALUE samples = rb_ary_new();
586
- rb_ivar_set(result, rb_intern("@samples"), samples);
587
- VALUE weights = rb_ary_new();
588
- rb_ivar_set(result, rb_intern("@weights"), weights);
979
+ VALUE threads = rb_hash_new();
980
+ rb_ivar_set(result, rb_intern("@threads"), threads);
589
981
 
590
- for (auto& stack_index: this->samples) {
591
- rb_ary_push(samples, INT2NUM(stack_index));
592
- rb_ary_push(weights, INT2NUM(1));
593
- }
982
+ VALUE thread_hash = rb_hash_new();
983
+ samples.write_result(thread_hash);
984
+
985
+ rb_hash_aset(threads, ULL2NUM(0), thread_hash);
986
+ rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
594
987
 
595
988
  frame_list.write_result(result);
596
989
 
@@ -623,16 +1016,18 @@ class RetainedCollector : public BaseCollector {
623
1016
 
624
1017
  static void newobj_i(VALUE tpval, void *data) {
625
1018
  RetainedCollector *collector = static_cast<RetainedCollector *>(data);
626
- TraceArg tp(tpval);
1019
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1020
+ VALUE obj = rb_tracearg_object(tparg);
627
1021
 
628
- collector->record(tp.obj);
1022
+ collector->record(obj);
629
1023
  }
630
1024
 
631
1025
  static void freeobj_i(VALUE tpval, void *data) {
632
1026
  RetainedCollector *collector = static_cast<RetainedCollector *>(data);
633
- TraceArg tp(tpval);
1027
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1028
+ VALUE obj = rb_tracearg_object(tparg);
634
1029
 
635
- collector->object_frames.erase(tp.obj);
1030
+ collector->object_frames.erase(obj);
636
1031
  }
637
1032
 
638
1033
  public:
@@ -687,12 +1082,21 @@ class RetainedCollector : public BaseCollector {
687
1082
  RetainedCollector *collector = this;
688
1083
  FrameList &frame_list = collector->frame_list;
689
1084
 
690
- VALUE result = rb_obj_alloc(rb_cVernierResult);
1085
+ VALUE result = BaseCollector::build_collector_result();
1086
+
1087
+ VALUE threads = rb_hash_new();
1088
+ rb_ivar_set(result, rb_intern("@threads"), threads);
1089
+ VALUE thread_hash = rb_hash_new();
1090
+ rb_hash_aset(threads, ULL2NUM(0), thread_hash);
691
1091
 
1092
+ rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
692
1093
  VALUE samples = rb_ary_new();
693
- rb_ivar_set(result, rb_intern("@samples"), samples);
1094
+ rb_hash_aset(thread_hash, sym("samples"), samples);
694
1095
  VALUE weights = rb_ary_new();
695
- rb_ivar_set(result, rb_intern("@weights"), weights);
1096
+ rb_hash_aset(thread_hash, sym("weights"), weights);
1097
+
1098
+ rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("retained memory"));
1099
+ rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(collector->started_at.nanoseconds()));
696
1100
 
697
1101
  for (auto& obj: collector->object_list) {
698
1102
  const auto search = collector->object_frames.find(obj);
@@ -721,162 +1125,68 @@ class RetainedCollector : public BaseCollector {
721
1125
  }
722
1126
  };
723
1127
 
724
- typedef uint64_t native_thread_id_t;
1128
+ class GlobalSignalHandler {
1129
+ static LiveSample *live_sample;
725
1130
 
726
- class Thread {
727
1131
  public:
728
- static native_thread_id_t get_native_thread_id() {
729
- #ifdef __APPLE__
730
- uint64_t thread_id;
731
- int e = pthread_threadid_np(pthread_self(), &thread_id);
732
- if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
733
- return thread_id;
734
- #else
735
- // gettid() is only available as of glibc 2.30
736
- pid_t tid = syscall(SYS_gettid);
737
- return tid;
738
- #endif
739
- }
740
-
741
- enum State {
742
- STARTED,
743
- RUNNING,
744
- SUSPENDED,
745
- STOPPED
746
- };
747
-
748
- pthread_t pthread_id;
749
- native_thread_id_t native_tid;
750
- State state;
751
-
752
- TimeStamp state_changed_at;
753
- TimeStamp started_at;
754
- TimeStamp stopped_at;
755
-
756
- RawSample stack_on_suspend;
757
-
758
- std::string name;
759
-
760
- Thread(State state) : state(state) {
761
- pthread_id = pthread_self();
762
- native_tid = get_native_thread_id();
763
- started_at = state_changed_at = TimeStamp::Now();
1132
+ static GlobalSignalHandler *get_instance() {
1133
+ static GlobalSignalHandler instance;
1134
+ return &instance;
764
1135
  }
765
1136
 
766
- void set_state(State new_state) {
767
- if (state == Thread::State::STOPPED) {
768
- return;
769
- }
770
-
771
- auto now = TimeStamp::Now();
772
-
773
- state = new_state;
774
- state_changed_at = now;
775
- if (new_state == State::STARTED) {
776
- if (started_at.zero()) {
777
- started_at = now;
778
- }
779
- } else if (new_state == State::STOPPED) {
780
- stopped_at = now;
1137
+ void install() {
1138
+ const std::lock_guard<std::mutex> lock(mutex);
1139
+ count++;
781
1140
 
782
- capture_name();
783
- }
1141
+ if (count == 1) setup_signal_handler();
784
1142
  }
785
1143
 
786
- bool running() {
787
- return state != State::STOPPED;
788
- }
1144
+ void uninstall() {
1145
+ const std::lock_guard<std::mutex> lock(mutex);
1146
+ count--;
789
1147
 
790
- void capture_name() {
791
- char buf[128];
792
- int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
793
- if (rc == 0)
794
- name = std::string(buf);
1148
+ if (count == 0) clear_signal_handler();
795
1149
  }
796
- };
797
-
798
- class Marker {
799
- public:
800
- enum Type {
801
- MARKER_GVL_THREAD_STARTED,
802
- MARKER_GVL_THREAD_READY,
803
- MARKER_GVL_THREAD_RESUMED,
804
- MARKER_GVL_THREAD_SUSPENDED,
805
- MARKER_GVL_THREAD_EXITED,
806
1150
 
807
- MARKER_GC_START,
808
- MARKER_GC_END_MARK,
809
- MARKER_GC_END_SWEEP,
810
- MARKER_GC_ENTER,
811
- MARKER_GC_EXIT,
812
-
813
- MARKER_MAX,
814
- };
815
- Type type;
816
- TimeStamp timestamp;
817
- native_thread_id_t thread_id;
818
- };
819
-
820
- class MarkerTable {
821
- public:
822
- std::vector<Marker> list;
823
- std::mutex mutex;
824
-
825
- void record(Marker::Type type) {
1151
+ void record_sample(LiveSample &sample, pthread_t pthread_id) {
826
1152
  const std::lock_guard<std::mutex> lock(mutex);
827
1153
 
828
- list.push_back({ type, TimeStamp::Now(), Thread::get_native_thread_id() });
1154
+ live_sample = &sample;
1155
+ if (pthread_kill(pthread_id, SIGPROF)) {
1156
+ rb_bug("pthread_kill failed");
1157
+ }
1158
+ sample.wait();
1159
+ live_sample = NULL;
829
1160
  }
830
- };
831
1161
 
832
- extern "C" int ruby_thread_has_gvl_p(void);
833
-
834
- class ThreadTable {
835
- public:
836
- std::vector<Thread> list;
1162
+ private:
837
1163
  std::mutex mutex;
1164
+ int count;
838
1165
 
839
- void started() {
840
- //const std::lock_guard<std::mutex> lock(mutex);
841
-
842
- //list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
843
- set_state(Thread::State::STARTED);
1166
+ static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
1167
+ assert(live_sample);
1168
+ live_sample->sample_current_thread();
844
1169
  }
845
1170
 
846
- void set_state(Thread::State new_state) {
847
- const std::lock_guard<std::mutex> lock(mutex);
848
-
849
- pthread_t current_thread = pthread_self();
850
- //cerr << "set state=" << new_state << " thread=" << gettid() << endl;
851
-
852
- for (auto &thread : list) {
853
- if (pthread_equal(current_thread, thread.pthread_id)) {
854
- thread.set_state(new_state);
855
-
856
- if (new_state == Thread::State::SUSPENDED) {
857
- thread.stack_on_suspend.sample();
858
- //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
859
- }
860
- return;
861
- }
862
- }
863
-
864
- pid_t native_tid = Thread::get_native_thread_id();
865
- list.emplace_back(new_state);
1171
+ void setup_signal_handler() {
1172
+ struct sigaction sa;
1173
+ sa.sa_sigaction = signal_handler;
1174
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
1175
+ sigemptyset(&sa.sa_mask);
1176
+ sigaction(SIGPROF, &sa, NULL);
866
1177
  }
867
- };
868
1178
 
869
- enum Category{
870
- CATEGORY_NORMAL,
871
- CATEGORY_IDLE
1179
+ void clear_signal_handler() {
1180
+ struct sigaction sa;
1181
+ sa.sa_handler = SIG_IGN;
1182
+ sa.sa_flags = SA_RESTART;
1183
+ sigemptyset(&sa.sa_mask);
1184
+ sigaction(SIGPROF, &sa, NULL);
1185
+ }
872
1186
  };
1187
+ LiveSample *GlobalSignalHandler::live_sample;
873
1188
 
874
1189
  class TimeCollector : public BaseCollector {
875
- std::vector<int> samples;
876
- std::vector<TimeStamp> timestamps;
877
- std::vector<native_thread_id_t> sample_threads;
878
- std::vector<Category> sample_categories;
879
-
880
1190
  MarkerTable markers;
881
1191
  ThreadTable threads;
882
1192
 
@@ -885,41 +1195,31 @@ class TimeCollector : public BaseCollector {
885
1195
  atomic_bool running;
886
1196
  SamplerSemaphore thread_stopped;
887
1197
 
888
- static LiveSample *live_sample;
889
-
890
- TimeStamp started_at;
891
1198
  TimeStamp interval;
892
1199
 
893
1200
  public:
894
- TimeCollector(TimeStamp interval) : interval(interval) {
1201
+ TimeCollector(TimeStamp interval) : interval(interval), threads(frame_list) {
895
1202
  }
896
1203
 
897
1204
  private:
898
1205
 
899
- void record_sample(const RawSample &sample, TimeStamp time, const Thread &thread, Category category) {
1206
+ void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
900
1207
  if (!sample.empty()) {
901
- int stack_index = frame_list.stack_index(sample);
902
- samples.push_back(stack_index);
903
- timestamps.push_back(time);
904
- sample_threads.push_back(thread.native_tid);
905
- sample_categories.push_back(category);
1208
+ int stack_index = thread.translator.translate(frame_list, sample);
1209
+ thread.samples.record_sample(
1210
+ stack_index,
1211
+ time,
1212
+ thread.native_tid,
1213
+ category
1214
+ );
906
1215
  }
907
1216
  }
908
1217
 
909
- static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
910
- assert(live_sample);
911
- live_sample->sample_current_thread();
912
- }
913
-
914
1218
  VALUE get_markers() {
915
- VALUE list = rb_ary_new();
1219
+ VALUE list = rb_ary_new2(this->markers.list.size());
916
1220
 
917
1221
  for (auto& marker: this->markers.list) {
918
- VALUE record[3] = {0};
919
- record[0] = ULL2NUM(marker.thread_id);
920
- record[1] = INT2NUM(marker.type);
921
- record[2] = ULL2NUM(marker.timestamp.nanoseconds());
922
- rb_ary_push(list, rb_ary_new_from_values(3, record));
1222
+ rb_ary_push(list, marker.to_array());
923
1223
  }
924
1224
 
925
1225
  return list;
@@ -927,20 +1227,16 @@ class TimeCollector : public BaseCollector {
927
1227
 
928
1228
  void sample_thread_run() {
929
1229
  LiveSample sample;
930
- live_sample = &sample;
931
1230
 
932
1231
  TimeStamp next_sample_schedule = TimeStamp::Now();
933
1232
  while (running) {
934
1233
  TimeStamp sample_start = TimeStamp::Now();
935
1234
 
936
1235
  threads.mutex.lock();
937
- for (auto thread : threads.list) {
1236
+ for (auto &thread : threads.list) {
938
1237
  //if (thread.state == Thread::State::RUNNING) {
939
- if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend.size() == 0)) {
940
- if (pthread_kill(thread.pthread_id, SIGPROF)) {
941
- rb_bug("pthread_kill failed");
942
- }
943
- sample.wait();
1238
+ if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
1239
+ GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
944
1240
 
945
1241
  if (sample.sample.gc) {
946
1242
  // fprintf(stderr, "skipping GC sample\n");
@@ -948,27 +1244,29 @@ class TimeCollector : public BaseCollector {
948
1244
  record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
949
1245
  }
950
1246
  } else if (thread.state == Thread::State::SUSPENDED) {
951
- record_sample(thread.stack_on_suspend, sample_start, thread, CATEGORY_IDLE);
1247
+ thread.samples.record_sample(
1248
+ thread.stack_on_suspend_idx,
1249
+ sample_start,
1250
+ thread.native_tid,
1251
+ CATEGORY_IDLE);
952
1252
  } else {
953
1253
  }
954
1254
  }
1255
+
955
1256
  threads.mutex.unlock();
956
1257
 
957
1258
  TimeStamp sample_complete = TimeStamp::Now();
958
1259
 
959
1260
  next_sample_schedule += interval;
960
1261
 
1262
+ // If sampling falls behind, restart, and check in another interval
961
1263
  if (next_sample_schedule < sample_complete) {
962
- //fprintf(stderr, "fell behind by %ius\n", (sample_complete - next_sample_schedule).microseconds());
963
1264
  next_sample_schedule = sample_complete + interval;
964
1265
  }
965
1266
 
966
- TimeStamp sleep_time = next_sample_schedule - sample_complete;
967
- TimeStamp::Sleep(sleep_time);
1267
+ TimeStamp::SleepUntil(next_sample_schedule);
968
1268
  }
969
1269
 
970
- live_sample = NULL;
971
-
972
1270
  thread_stopped.post();
973
1271
  }
974
1272
 
@@ -978,10 +1276,21 @@ class TimeCollector : public BaseCollector {
978
1276
  return NULL;
979
1277
  }
980
1278
 
981
- static void internal_gc_event_cb(VALUE tpval, void *data) {
982
- TimeCollector *collector = static_cast<TimeCollector *>(data);
983
- rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
984
- int event = rb_tracearg_event_flag(tparg);
1279
+ static void internal_thread_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
1280
+ TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
1281
+
1282
+ switch (event) {
1283
+ case RUBY_EVENT_THREAD_BEGIN:
1284
+ collector->threads.started(&collector->markers);
1285
+ break;
1286
+ case RUBY_EVENT_THREAD_END:
1287
+ collector->threads.stopped(&collector->markers);
1288
+ break;
1289
+ }
1290
+ }
1291
+
1292
+ static void internal_gc_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
1293
+ TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
985
1294
 
986
1295
  switch (event) {
987
1296
  case RUBY_INTERNAL_EVENT_GC_START:
@@ -994,10 +1303,10 @@ class TimeCollector : public BaseCollector {
994
1303
  collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
995
1304
  break;
996
1305
  case RUBY_INTERNAL_EVENT_GC_ENTER:
997
- collector->markers.record(Marker::Type::MARKER_GC_ENTER);
1306
+ collector->markers.record_gc_entered();
998
1307
  break;
999
1308
  case RUBY_INTERNAL_EVENT_GC_EXIT:
1000
- collector->markers.record(Marker::Type::MARKER_GC_EXIT);
1309
+ collector->markers.record_gc_leave();
1001
1310
  break;
1002
1311
  }
1003
1312
  }
@@ -1007,44 +1316,27 @@ class TimeCollector : public BaseCollector {
1007
1316
  //cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
1008
1317
 
1009
1318
  switch (event) {
1010
- case RUBY_INTERNAL_THREAD_EVENT_STARTED:
1011
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_STARTED);
1012
- collector->threads.started();
1013
- break;
1014
1319
  case RUBY_INTERNAL_THREAD_EVENT_READY:
1015
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_READY);
1320
+ collector->threads.ready(&collector->markers);
1016
1321
  break;
1017
1322
  case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
1018
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_RESUMED);
1019
- collector->threads.set_state(Thread::State::RUNNING);
1323
+ collector->threads.resumed(&collector->markers);
1020
1324
  break;
1021
1325
  case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
1022
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_SUSPENDED);
1023
- collector->threads.set_state(Thread::State::SUSPENDED);
1024
- break;
1025
- case RUBY_INTERNAL_THREAD_EVENT_EXITED:
1026
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_EXITED);
1027
- collector->threads.set_state(Thread::State::STOPPED);
1326
+ collector->threads.suspended(&collector->markers);
1028
1327
  break;
1029
1328
 
1030
1329
  }
1031
1330
  }
1032
1331
 
1033
1332
  rb_internal_thread_event_hook_t *thread_hook;
1034
- VALUE gc_hook;
1035
1333
 
1036
1334
  bool start() {
1037
1335
  if (!BaseCollector::start()) {
1038
1336
  return false;
1039
1337
  }
1040
1338
 
1041
- started_at = TimeStamp::Now();
1042
-
1043
- struct sigaction sa;
1044
- sa.sa_sigaction = signal_handler;
1045
- sa.sa_flags = SA_RESTART | SA_SIGINFO;
1046
- sigemptyset(&sa.sa_mask);
1047
- sigaction(SIGPROF, &sa, NULL);
1339
+ GlobalSignalHandler::get_instance()->install();
1048
1340
 
1049
1341
  running = true;
1050
1342
 
@@ -1054,15 +1346,16 @@ class TimeCollector : public BaseCollector {
1054
1346
  rb_bug("pthread_create");
1055
1347
  }
1056
1348
 
1057
- // Set the state of the current Ruby thread to RUNNING.
1058
- // We want to have at least one thread in our thread list because it's
1059
- // possible that the profile might be such that we don't get any
1060
- // thread switch events and we need at least one
1061
- this->threads.set_state(Thread::State::RUNNING);
1349
+ // Set the state of the current Ruby thread to RUNNING, which we know it
1350
+ // is as it must have held the GVL to start the collector. We want to
1351
+ // have at least one thread in our thread list because it's possible
1352
+ // that the profile might be such that we don't get any thread switch
1353
+ // events and we need at least one
1354
+ this->threads.resumed(&this->markers);
1062
1355
 
1063
1356
  thread_hook = rb_internal_thread_add_event_hook(internal_thread_event_cb, RUBY_INTERNAL_THREAD_EVENT_MASK, this);
1064
- gc_hook = rb_tracepoint_new(0, RUBY_GC_PHASE_EVENTS, internal_gc_event_cb, (void *)this);
1065
- rb_tracepoint_enable(gc_hook);
1357
+ rb_add_event_hook(internal_gc_event_cb, RUBY_INTERNAL_EVENTS, PTR2NUM((void *)this));
1358
+ rb_add_event_hook(internal_thread_event_cb, RUBY_NORMAL_EVENTS, PTR2NUM((void *)this));
1066
1359
 
1067
1360
  return true;
1068
1361
  }
@@ -1073,14 +1366,11 @@ class TimeCollector : public BaseCollector {
1073
1366
  running = false;
1074
1367
  thread_stopped.wait();
1075
1368
 
1076
- struct sigaction sa;
1077
- sa.sa_handler = SIG_IGN;
1078
- sa.sa_flags = SA_RESTART;
1079
- sigemptyset(&sa.sa_mask);
1080
- sigaction(SIGPROF, &sa, NULL);
1369
+ GlobalSignalHandler::get_instance()->uninstall();
1081
1370
 
1082
1371
  rb_internal_thread_remove_event_hook(thread_hook);
1083
- rb_tracepoint_disable(gc_hook);
1372
+ rb_remove_event_hook(internal_gc_event_cb);
1373
+ rb_remove_event_hook(internal_thread_event_cb);
1084
1374
 
1085
1375
  // capture thread names
1086
1376
  for (auto& thread: this->threads.list) {
@@ -1099,45 +1389,15 @@ class TimeCollector : public BaseCollector {
1099
1389
  }
1100
1390
 
1101
1391
  VALUE build_collector_result() {
1102
- VALUE result = rb_obj_alloc(rb_cVernierResult);
1103
-
1104
- VALUE meta = rb_hash_new();
1105
- rb_ivar_set(result, rb_intern("@meta"), meta);
1106
- rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
1107
-
1108
- VALUE samples = rb_ary_new();
1109
- rb_ivar_set(result, rb_intern("@samples"), samples);
1110
- VALUE weights = rb_ary_new();
1111
- rb_ivar_set(result, rb_intern("@weights"), weights);
1112
- for (auto& stack_index: this->samples) {
1113
- rb_ary_push(samples, INT2NUM(stack_index));
1114
- rb_ary_push(weights, INT2NUM(1));
1115
- }
1116
-
1117
- VALUE timestamps = rb_ary_new();
1118
- rb_ivar_set(result, rb_intern("@timestamps"), timestamps);
1119
-
1120
- for (auto& timestamp: this->timestamps) {
1121
- rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
1122
- }
1123
-
1124
- VALUE sample_threads = rb_ary_new();
1125
- rb_ivar_set(result, rb_intern("@sample_threads"), sample_threads);
1126
- for (auto& thread: this->sample_threads) {
1127
- rb_ary_push(sample_threads, ULL2NUM(thread));
1128
- }
1129
-
1130
- VALUE sample_categories = rb_ary_new();
1131
- rb_ivar_set(result, rb_intern("@sample_categories"), sample_categories);
1132
- for (auto& cat: this->sample_categories) {
1133
- rb_ary_push(sample_categories, INT2NUM(cat));
1134
- }
1392
+ VALUE result = BaseCollector::build_collector_result();
1135
1393
 
1136
1394
  VALUE threads = rb_hash_new();
1137
1395
  rb_ivar_set(result, rb_intern("@threads"), threads);
1138
1396
 
1139
1397
  for (const auto& thread: this->threads.list) {
1140
1398
  VALUE hash = rb_hash_new();
1399
+ thread.samples.write_result(hash);
1400
+
1141
1401
  rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
1142
1402
  rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
1143
1403
  rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
@@ -1155,7 +1415,6 @@ class TimeCollector : public BaseCollector {
1155
1415
 
1156
1416
  void mark() {
1157
1417
  frame_list.mark_frames();
1158
- rb_gc_mark(gc_hook);
1159
1418
 
1160
1419
  //for (int i = 0; i < queued_length; i++) {
1161
1420
  // rb_gc_mark(queued_frames[i]);
@@ -1165,8 +1424,6 @@ class TimeCollector : public BaseCollector {
1165
1424
  }
1166
1425
  };
1167
1426
 
1168
- LiveSample *TimeCollector::live_sample;
1169
-
1170
1427
  static void
1171
1428
  collector_mark(void *data) {
1172
1429
  BaseCollector *collector = static_cast<BaseCollector *>(data);
@@ -1252,14 +1509,11 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
1252
1509
  }
1253
1510
 
1254
1511
  static void
1255
- Init_consts() {
1512
+ Init_consts(VALUE rb_mVernierMarkerPhase) {
1256
1513
  #define MARKER_CONST(name) \
1257
1514
  rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
1258
1515
 
1259
1516
  MARKER_CONST(GVL_THREAD_STARTED);
1260
- MARKER_CONST(GVL_THREAD_READY);
1261
- MARKER_CONST(GVL_THREAD_RESUMED);
1262
- MARKER_CONST(GVL_THREAD_SUSPENDED);
1263
1517
  MARKER_CONST(GVL_THREAD_EXITED);
1264
1518
 
1265
1519
  MARKER_CONST(GC_START);
@@ -1267,8 +1521,22 @@ Init_consts() {
1267
1521
  MARKER_CONST(GC_END_SWEEP);
1268
1522
  MARKER_CONST(GC_ENTER);
1269
1523
  MARKER_CONST(GC_EXIT);
1524
+ MARKER_CONST(GC_PAUSE);
1525
+
1526
+ MARKER_CONST(THREAD_RUNNING);
1527
+ MARKER_CONST(THREAD_STALLED);
1528
+ MARKER_CONST(THREAD_SUSPENDED);
1270
1529
 
1271
1530
  #undef MARKER_CONST
1531
+
1532
+ #define PHASE_CONST(name) \
1533
+ rb_define_const(rb_mVernierMarkerPhase, #name, INT2NUM(Marker::Phase::name))
1534
+
1535
+ PHASE_CONST(INSTANT);
1536
+ PHASE_CONST(INTERVAL);
1537
+ PHASE_CONST(INTERVAL_START);
1538
+ PHASE_CONST(INTERVAL_END);
1539
+ #undef PHASE_CONST
1272
1540
  }
1273
1541
 
1274
1542
  extern "C" void
@@ -1277,6 +1545,7 @@ Init_vernier(void)
1277
1545
  rb_mVernier = rb_define_module("Vernier");
1278
1546
  rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
1279
1547
  VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
1548
+ VALUE rb_mVernierMarkerPhase = rb_define_module_under(rb_mVernierMarker, "Phase");
1280
1549
  rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
1281
1550
 
1282
1551
  rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
@@ -1287,7 +1556,7 @@ Init_vernier(void)
1287
1556
  rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
1288
1557
  rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
1289
1558
 
1290
- Init_consts();
1559
+ Init_consts(rb_mVernierMarkerPhase);
1291
1560
 
1292
1561
  //static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
1293
1562
  //rb_global_variable(&gc_hook);