vernier 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);