vernier 0.2.0 → 0.3.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.
@@ -9,31 +9,38 @@
9
9
  #include <cassert>
10
10
  #include <atomic>
11
11
  #include <mutex>
12
- #include <optional>
13
12
 
14
13
  #include <sys/time.h>
15
14
  #include <signal.h>
16
15
  #ifdef __APPLE__
16
+ /* macOS */
17
17
  #include <dispatch/dispatch.h>
18
18
  #else
19
+ /* Linux */
19
20
  #include <semaphore.h>
21
+ #include <sys/syscall.h> /* for SYS_gettid */
20
22
  #endif
21
23
 
22
24
  #include "vernier.hh"
23
- #include "stack.hh"
24
25
 
25
26
  #include "ruby/ruby.h"
26
27
  #include "ruby/debug.h"
27
28
  #include "ruby/thread.h"
28
29
 
29
- // GC event's we'll monitor during profiling
30
- #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 \
31
34
  RUBY_INTERNAL_EVENT_GC_START | \
32
35
  RUBY_INTERNAL_EVENT_GC_END_MARK | \
33
36
  RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
34
37
  RUBY_INTERNAL_EVENT_GC_ENTER | \
35
38
  RUBY_INTERNAL_EVENT_GC_EXIT
36
39
 
40
+ #define RUBY_NORMAL_EVENTS \
41
+ RUBY_EVENT_THREAD_BEGIN | \
42
+ RUBY_EVENT_THREAD_END
43
+
37
44
  #define sym(name) ID2SYM(rb_intern_const(name))
38
45
 
39
46
  // HACK: This isn't public, but the objspace ext uses it
@@ -124,6 +131,14 @@ class TimeStamp {
124
131
  return value_ns >= other.value_ns;
125
132
  }
126
133
 
134
+ bool operator==(const TimeStamp &other) const {
135
+ return value_ns == other.value_ns;
136
+ }
137
+
138
+ bool operator!=(const TimeStamp &other) const {
139
+ return value_ns != other.value_ns;
140
+ }
141
+
127
142
  uint64_t nanoseconds() const {
128
143
  return value_ns;
129
144
  }
@@ -149,6 +164,73 @@ std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
149
164
  return os;
150
165
  }
151
166
 
167
+ struct FrameInfo {
168
+ static const char *label_cstr(VALUE frame) {
169
+ VALUE label = rb_profile_frame_full_label(frame);
170
+ return StringValueCStr(label);
171
+ }
172
+
173
+ static const char *file_cstr(VALUE frame) {
174
+ VALUE file = rb_profile_frame_absolute_path(frame);
175
+ if (NIL_P(file))
176
+ file = rb_profile_frame_path(frame);
177
+ if (NIL_P(file)) {
178
+ return "";
179
+ } else {
180
+ return StringValueCStr(file);
181
+ }
182
+ }
183
+
184
+ static int first_lineno_int(VALUE frame) {
185
+ VALUE first_lineno = rb_profile_frame_first_lineno(frame);
186
+ return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
187
+ }
188
+
189
+ FrameInfo(VALUE frame) :
190
+ label(label_cstr(frame)),
191
+ file(file_cstr(frame)),
192
+ first_lineno(first_lineno_int(frame)) { }
193
+
194
+ std::string label;
195
+ std::string file;
196
+ int first_lineno;
197
+ };
198
+
199
+ bool operator==(const FrameInfo& lhs, const FrameInfo& rhs) noexcept {
200
+ return
201
+ lhs.label == rhs.label &&
202
+ lhs.file == rhs.file &&
203
+ lhs.first_lineno == rhs.first_lineno;
204
+ }
205
+
206
+ struct Frame {
207
+ VALUE frame;
208
+ int line;
209
+
210
+ FrameInfo info() const {
211
+ return FrameInfo(frame);
212
+ }
213
+ };
214
+
215
+ bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
216
+ return lhs.frame == rhs.frame && lhs.line == rhs.line;
217
+ }
218
+
219
+ bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
220
+ return !(lhs == rhs);
221
+ }
222
+
223
+ namespace std {
224
+ template<>
225
+ struct hash<Frame>
226
+ {
227
+ std::size_t operator()(Frame const& s) const noexcept
228
+ {
229
+ return s.frame ^ s.line;
230
+ }
231
+ };
232
+ }
233
+
152
234
  // A basic semaphore built on sem_wait/sem_post
153
235
  // post() is guaranteed to be async-signal-safe
154
236
  class SamplerSemaphore {
@@ -211,7 +293,9 @@ struct RawSample {
211
293
  }
212
294
 
213
295
  Frame frame(int i) const {
214
- const Frame frame = {frames[i], lines[i]};
296
+ int idx = len - i - 1;
297
+ if (idx < 0) throw std::out_of_range("out of range");
298
+ const Frame frame = {frames[idx], lines[idx]};
215
299
  return frame;
216
300
  }
217
301
 
@@ -273,24 +357,6 @@ struct LiveSample {
273
357
  }
274
358
  };
275
359
 
276
- struct TraceArg {
277
- rb_trace_arg_t *tparg;
278
- VALUE obj;
279
- VALUE path;
280
- VALUE line;
281
- VALUE mid;
282
- VALUE klass;
283
-
284
- TraceArg(VALUE tpval) {
285
- tparg = rb_tracearg_from_tracepoint(tpval);
286
- obj = rb_tracearg_object(tparg);
287
- path = rb_tracearg_path(tparg);
288
- line = rb_tracearg_lineno(tparg);
289
- mid = rb_tracearg_method_id(tparg);
290
- klass = rb_tracearg_defined_class(tparg);
291
- }
292
- };
293
-
294
360
  struct FrameList {
295
361
  std::unordered_map<std::string, int> string_to_idx;
296
362
  std::vector<std::string> string_list;
@@ -348,27 +414,29 @@ struct FrameList {
348
414
  }
349
415
 
350
416
  StackNode *node = &root_stack_node;
351
- for (int i = stack.size() - 1; i >= 0; i--) {
352
- const Frame &frame = stack.frame(i);
417
+ for (int i = 0; i < stack.size(); i++) {
418
+ Frame frame = stack.frame(i);
353
419
  node = next_stack_node(node, frame);
354
420
  }
355
421
  return node->index;
356
422
  }
357
423
 
358
- StackNode *next_stack_node(StackNode *node, const Frame &frame) {
359
- int next_node_idx = node->children[frame];
360
- if (next_node_idx == 0) {
424
+ StackNode *next_stack_node(StackNode *node, Frame frame) {
425
+ auto search = node->children.find(frame);
426
+ if (search == node->children.end()) {
361
427
  // insert a new node
362
- next_node_idx = stack_node_list.size();
428
+ int next_node_idx = stack_node_list.size();
363
429
  node->children[frame] = next_node_idx;
364
430
  stack_node_list.emplace_back(
365
431
  frame,
366
432
  next_node_idx,
367
433
  node->index
368
434
  );
435
+ return &stack_node_list[next_node_idx];
436
+ } else {
437
+ int node_idx = search->second;
438
+ return &stack_node_list[node_idx];
369
439
  }
370
-
371
- return &stack_node_list[next_node_idx];
372
440
  }
373
441
 
374
442
  // Converts Frames from stacks other tables. "Symbolicates" the frames
@@ -448,6 +516,372 @@ struct FrameList {
448
516
  }
449
517
  };
450
518
 
519
+ class SampleTranslator {
520
+ public:
521
+ int last_stack_index;
522
+
523
+ Frame frames[RawSample::MAX_LEN];
524
+ int frame_indexes[RawSample::MAX_LEN];
525
+ int len;
526
+
527
+ SampleTranslator() : len(0), last_stack_index(-1) {
528
+ }
529
+
530
+ int translate(FrameList &frame_list, const RawSample &sample) {
531
+ int i = 0;
532
+ for (; i < len && i < sample.size(); i++) {
533
+ if (frames[i] != sample.frame(i)) {
534
+ break;
535
+ }
536
+ }
537
+
538
+ FrameList::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
539
+
540
+ for (; i < sample.size(); i++) {
541
+ Frame frame = sample.frame(i);
542
+ node = frame_list.next_stack_node(node, frame);
543
+
544
+ frames[i] = frame;
545
+ frame_indexes[i] = node->index;
546
+ }
547
+ len = i;
548
+
549
+ last_stack_index = node->index;
550
+ return last_stack_index;
551
+ }
552
+ };
553
+
554
+ typedef uint64_t native_thread_id_t;
555
+ static native_thread_id_t get_native_thread_id() {
556
+ #ifdef __APPLE__
557
+ uint64_t thread_id;
558
+ int e = pthread_threadid_np(pthread_self(), &thread_id);
559
+ if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
560
+ return thread_id;
561
+ #else
562
+ // gettid() is only available as of glibc 2.30
563
+ pid_t tid = syscall(SYS_gettid);
564
+ return tid;
565
+ #endif
566
+ }
567
+
568
+
569
+ class Marker {
570
+ public:
571
+ enum Type {
572
+ MARKER_GVL_THREAD_STARTED,
573
+ MARKER_GVL_THREAD_EXITED,
574
+
575
+ MARKER_GC_START,
576
+ MARKER_GC_END_MARK,
577
+ MARKER_GC_END_SWEEP,
578
+ MARKER_GC_ENTER,
579
+ MARKER_GC_EXIT,
580
+ MARKER_GC_PAUSE,
581
+
582
+ MARKER_THREAD_RUNNING,
583
+ MARKER_THREAD_STALLED,
584
+ MARKER_THREAD_SUSPENDED,
585
+
586
+ MARKER_MAX,
587
+ };
588
+
589
+ // Must match phase types from Gecko
590
+ enum Phase {
591
+ INSTANT,
592
+ INTERVAL,
593
+ INTERVAL_START,
594
+ INTERVAL_END
595
+ };
596
+
597
+ Type type;
598
+ Phase phase;
599
+ TimeStamp timestamp;
600
+ TimeStamp finish;
601
+ native_thread_id_t thread_id;
602
+ int stack_index = -1;
603
+
604
+ VALUE to_array() {
605
+ VALUE record[6] = {0};
606
+ record[0] = ULL2NUM(thread_id);
607
+ record[1] = INT2NUM(type);
608
+ record[2] = INT2NUM(phase);
609
+ record[3] = ULL2NUM(timestamp.nanoseconds());
610
+
611
+ if (phase == Marker::Phase::INTERVAL) {
612
+ record[4] = ULL2NUM(finish.nanoseconds());
613
+ }
614
+ else {
615
+ record[4] = Qnil;
616
+ }
617
+ record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
618
+
619
+ return rb_ary_new_from_values(6, record);
620
+ }
621
+ };
622
+
623
+ class MarkerTable {
624
+ TimeStamp last_gc_entry;
625
+
626
+ public:
627
+ std::vector<Marker> list;
628
+ std::mutex mutex;
629
+
630
+ void record_gc_entered() {
631
+ last_gc_entry = TimeStamp::Now();
632
+ }
633
+
634
+ void record_gc_leave() {
635
+ list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), get_native_thread_id(), -1 });
636
+ }
637
+
638
+ void record_interval(Marker::Type type, TimeStamp from, TimeStamp to, int stack_index = -1) {
639
+ const std::lock_guard<std::mutex> lock(mutex);
640
+
641
+ list.push_back({ type, Marker::INTERVAL, from, to, get_native_thread_id(), stack_index });
642
+ }
643
+
644
+ void record(Marker::Type type, int stack_index = -1) {
645
+ const std::lock_guard<std::mutex> lock(mutex);
646
+
647
+ list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), get_native_thread_id(), stack_index });
648
+ }
649
+ };
650
+
651
+ enum Category{
652
+ CATEGORY_NORMAL,
653
+ CATEGORY_IDLE
654
+ };
655
+
656
+ class SampleList {
657
+ public:
658
+
659
+ std::vector<int> stacks;
660
+ std::vector<TimeStamp> timestamps;
661
+ std::vector<native_thread_id_t> threads;
662
+ std::vector<Category> categories;
663
+ std::vector<int> weights;
664
+
665
+ size_t size() {
666
+ return stacks.size();
667
+ }
668
+
669
+ bool empty() {
670
+ return size() == 0;
671
+ }
672
+
673
+ void record_sample(int stack_index, TimeStamp time, native_thread_id_t thread_id, Category category) {
674
+ if (
675
+ !empty() &&
676
+ stacks.back() == stack_index &&
677
+ threads.back() == thread_id &&
678
+ categories.back() == category)
679
+ {
680
+ // We don't compare timestamps for de-duplication
681
+ weights.back() += 1;
682
+ } else {
683
+ stacks.push_back(stack_index);
684
+ timestamps.push_back(time);
685
+ threads.push_back(thread_id);
686
+ categories.push_back(category);
687
+ weights.push_back(1);
688
+ }
689
+ }
690
+
691
+ void write_result(VALUE result) const {
692
+ VALUE samples = rb_ary_new();
693
+ rb_hash_aset(result, sym("samples"), samples);
694
+ for (auto& stack_index: this->stacks) {
695
+ rb_ary_push(samples, INT2NUM(stack_index));
696
+ }
697
+
698
+ VALUE weights = rb_ary_new();
699
+ rb_hash_aset(result, sym("weights"), weights);
700
+ for (auto& weight: this->weights) {
701
+ rb_ary_push(weights, INT2NUM(weight));
702
+ }
703
+
704
+ VALUE timestamps = rb_ary_new();
705
+ rb_hash_aset(result, sym("timestamps"), timestamps);
706
+ for (auto& timestamp: this->timestamps) {
707
+ rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
708
+ }
709
+
710
+ VALUE sample_categories = rb_ary_new();
711
+ rb_hash_aset(result, sym("sample_categories"), sample_categories);
712
+ for (auto& cat: this->categories) {
713
+ rb_ary_push(sample_categories, INT2NUM(cat));
714
+ }
715
+ }
716
+ };
717
+
718
+ class Thread {
719
+ public:
720
+ SampleList samples;
721
+
722
+ enum State {
723
+ STARTED,
724
+ RUNNING,
725
+ READY,
726
+ SUSPENDED,
727
+ STOPPED
728
+ };
729
+
730
+ pthread_t pthread_id;
731
+ native_thread_id_t native_tid;
732
+ State state;
733
+
734
+ TimeStamp state_changed_at;
735
+ TimeStamp started_at;
736
+ TimeStamp stopped_at;
737
+
738
+ int stack_on_suspend_idx;
739
+ SampleTranslator translator;
740
+
741
+ std::string name;
742
+
743
+ Thread(State state) : state(state), stack_on_suspend_idx(-1) {
744
+ pthread_id = pthread_self();
745
+ native_tid = get_native_thread_id();
746
+ started_at = state_changed_at = TimeStamp::Now();
747
+ }
748
+
749
+ void set_state(State new_state, MarkerTable *markers) {
750
+ if (state == Thread::State::STOPPED) {
751
+ return;
752
+ }
753
+
754
+ TimeStamp from = state_changed_at;
755
+ auto now = TimeStamp::Now();
756
+
757
+ if (started_at.zero()) {
758
+ started_at = now;
759
+ }
760
+
761
+ switch (new_state) {
762
+ case State::STARTED:
763
+ new_state = State::RUNNING;
764
+ break;
765
+ case State::RUNNING:
766
+ assert(state == State::READY);
767
+
768
+ // If the GVL is immediately ready, and we measure no times
769
+ // stalled, skip emitting the interval.
770
+ if (from != now) {
771
+ markers->record_interval(Marker::Type::MARKER_THREAD_STALLED, from, now);
772
+ }
773
+ break;
774
+ case State::READY:
775
+ // The ready state means "I would like to do some work, but I can't
776
+ // do it right now either because I blocked on IO and now I want the GVL back,
777
+ // or because the VM timer put me to sleep"
778
+ //
779
+ // Threads can be preempted, which means they will have been in "Running"
780
+ // state, and then the VM was like "no I need to stop you from working,
781
+ // so I'll put you in the 'ready' (or stalled) state"
782
+ assert(state == State::SUSPENDED || state == State::RUNNING);
783
+ if (state == State::SUSPENDED) {
784
+ markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
785
+ }
786
+ else {
787
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
788
+ }
789
+ break;
790
+ case State::SUSPENDED:
791
+ // We can go from RUNNING or STARTED to SUSPENDED
792
+ assert(state == State::RUNNING || state == State::STARTED);
793
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
794
+ break;
795
+ case State::STOPPED:
796
+ // We can go from RUNNING or STARTED to STOPPED
797
+ assert(state == State::RUNNING || state == State::STARTED);
798
+ markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
799
+ stopped_at = now;
800
+ capture_name();
801
+
802
+ break;
803
+ }
804
+
805
+ state = new_state;
806
+ state_changed_at = now;
807
+ }
808
+
809
+ bool running() {
810
+ return state != State::STOPPED;
811
+ }
812
+
813
+ void capture_name() {
814
+ char buf[128];
815
+ int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
816
+ if (rc == 0)
817
+ name = std::string(buf);
818
+ }
819
+ };
820
+
821
+ class ThreadTable {
822
+ public:
823
+ FrameList &frame_list;
824
+
825
+ std::vector<Thread> list;
826
+ std::mutex mutex;
827
+
828
+ ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
829
+ }
830
+
831
+ void started(MarkerTable *markers) {
832
+ //const std::lock_guard<std::mutex> lock(mutex);
833
+
834
+ //list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
835
+ markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
836
+ set_state(Thread::State::STARTED, markers);
837
+ }
838
+
839
+ void ready(MarkerTable *markers) {
840
+ set_state(Thread::State::READY, markers);
841
+ }
842
+
843
+ void resumed(MarkerTable *markers) {
844
+ set_state(Thread::State::RUNNING, markers);
845
+ }
846
+
847
+ void suspended(MarkerTable *markers) {
848
+ set_state(Thread::State::SUSPENDED, markers);
849
+ }
850
+
851
+ void stopped(MarkerTable *markers) {
852
+ markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
853
+ set_state(Thread::State::STOPPED, markers);
854
+ }
855
+
856
+ private:
857
+ void set_state(Thread::State new_state, MarkerTable *markers) {
858
+ const std::lock_guard<std::mutex> lock(mutex);
859
+
860
+ pthread_t current_thread = pthread_self();
861
+ //cerr << "set state=" << new_state << " thread=" << gettid() << endl;
862
+
863
+ for (auto &thread : list) {
864
+ if (pthread_equal(current_thread, thread.pthread_id)) {
865
+ if (new_state == Thread::State::SUSPENDED) {
866
+
867
+ RawSample sample;
868
+ sample.sample();
869
+
870
+ thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
871
+ //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
872
+ }
873
+
874
+ thread.set_state(new_state, markers);
875
+
876
+ return;
877
+ }
878
+ }
879
+
880
+ pid_t native_tid = get_native_thread_id();
881
+ list.emplace_back(new_state);
882
+ }
883
+ };
884
+
451
885
  class BaseCollector {
452
886
  protected:
453
887
 
@@ -459,13 +893,19 @@ class BaseCollector {
459
893
  bool running = false;
460
894
  FrameList frame_list;
461
895
 
896
+ TimeStamp started_at;
897
+
898
+ virtual ~BaseCollector() {}
899
+
462
900
  virtual bool start() {
463
901
  if (running) {
464
902
  return false;
465
- } else {
466
- running = true;
467
- return true;
468
903
  }
904
+
905
+ started_at = TimeStamp::Now();
906
+
907
+ running = true;
908
+ return true;
469
909
  }
470
910
 
471
911
  virtual VALUE stop() {
@@ -477,6 +917,21 @@ class BaseCollector {
477
917
  return Qnil;
478
918
  }
479
919
 
920
+ void write_meta(VALUE result) {
921
+ VALUE meta = rb_hash_new();
922
+ rb_ivar_set(result, rb_intern("@meta"), meta);
923
+ rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
924
+
925
+ }
926
+
927
+ virtual VALUE build_collector_result() {
928
+ VALUE result = rb_obj_alloc(rb_cVernierResult);
929
+
930
+ write_meta(result);
931
+
932
+ return result;
933
+ }
934
+
480
935
  virtual void sample() {
481
936
  rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
482
937
  };
@@ -491,14 +946,15 @@ class BaseCollector {
491
946
  };
492
947
 
493
948
  class CustomCollector : public BaseCollector {
494
- std::vector<int> samples;
949
+ SampleList samples;
495
950
 
496
951
  void sample() {
497
952
  RawSample sample;
498
953
  sample.sample();
499
954
  int stack_index = frame_list.stack_index(sample);
500
955
 
501
- samples.push_back(stack_index);
956
+ native_thread_id_t thread_id = 0;
957
+ samples.record_sample(stack_index, TimeStamp::Now(), thread_id, CATEGORY_NORMAL);
502
958
  }
503
959
 
504
960
  VALUE stop() {
@@ -514,17 +970,16 @@ class CustomCollector : public BaseCollector {
514
970
  }
515
971
 
516
972
  VALUE build_collector_result() {
517
- VALUE result = rb_obj_alloc(rb_cVernierResult);
973
+ VALUE result = BaseCollector::build_collector_result();
518
974
 
519
- VALUE samples = rb_ary_new();
520
- rb_ivar_set(result, rb_intern("@samples"), samples);
521
- VALUE weights = rb_ary_new();
522
- rb_ivar_set(result, rb_intern("@weights"), weights);
975
+ VALUE threads = rb_hash_new();
976
+ rb_ivar_set(result, rb_intern("@threads"), threads);
523
977
 
524
- for (auto& stack_index: this->samples) {
525
- rb_ary_push(samples, INT2NUM(stack_index));
526
- rb_ary_push(weights, INT2NUM(1));
527
- }
978
+ VALUE thread_hash = rb_hash_new();
979
+ samples.write_result(thread_hash);
980
+
981
+ rb_hash_aset(threads, ULL2NUM(0), thread_hash);
982
+ rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
528
983
 
529
984
  frame_list.write_result(result);
530
985
 
@@ -557,16 +1012,18 @@ class RetainedCollector : public BaseCollector {
557
1012
 
558
1013
  static void newobj_i(VALUE tpval, void *data) {
559
1014
  RetainedCollector *collector = static_cast<RetainedCollector *>(data);
560
- TraceArg tp(tpval);
1015
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1016
+ VALUE obj = rb_tracearg_object(tparg);
561
1017
 
562
- collector->record(tp.obj);
1018
+ collector->record(obj);
563
1019
  }
564
1020
 
565
1021
  static void freeobj_i(VALUE tpval, void *data) {
566
1022
  RetainedCollector *collector = static_cast<RetainedCollector *>(data);
567
- TraceArg tp(tpval);
1023
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1024
+ VALUE obj = rb_tracearg_object(tparg);
568
1025
 
569
- collector->object_frames.erase(tp.obj);
1026
+ collector->object_frames.erase(obj);
570
1027
  }
571
1028
 
572
1029
  public:
@@ -621,12 +1078,18 @@ class RetainedCollector : public BaseCollector {
621
1078
  RetainedCollector *collector = this;
622
1079
  FrameList &frame_list = collector->frame_list;
623
1080
 
624
- VALUE result = rb_obj_alloc(rb_cVernierResult);
1081
+ VALUE result = BaseCollector::build_collector_result();
625
1082
 
1083
+ VALUE threads = rb_hash_new();
1084
+ rb_ivar_set(result, rb_intern("@threads"), threads);
1085
+ VALUE thread_hash = rb_hash_new();
1086
+ rb_hash_aset(threads, ULL2NUM(0), thread_hash);
1087
+
1088
+ rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
626
1089
  VALUE samples = rb_ary_new();
627
- rb_ivar_set(result, rb_intern("@samples"), samples);
1090
+ rb_hash_aset(thread_hash, sym("samples"), samples);
628
1091
  VALUE weights = rb_ary_new();
629
- rb_ivar_set(result, rb_intern("@weights"), weights);
1092
+ rb_hash_aset(thread_hash, sym("weights"), weights);
630
1093
 
631
1094
  for (auto& obj: collector->object_list) {
632
1095
  const auto search = collector->object_frames.find(obj);
@@ -655,160 +1118,68 @@ class RetainedCollector : public BaseCollector {
655
1118
  }
656
1119
  };
657
1120
 
658
- typedef uint64_t native_thread_id_t;
1121
+ class GlobalSignalHandler {
1122
+ static LiveSample *live_sample;
659
1123
 
660
- class Thread {
661
1124
  public:
662
- static native_thread_id_t get_native_thread_id() {
663
- #ifdef __APPLE__
664
- uint64_t thread_id;
665
- int e = pthread_threadid_np(pthread_self(), &thread_id);
666
- if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
667
- return thread_id;
668
- #else
669
- return gettid();
670
- #endif
1125
+ static GlobalSignalHandler *get_instance() {
1126
+ static GlobalSignalHandler instance;
1127
+ return &instance;
671
1128
  }
672
1129
 
673
- enum State {
674
- STARTED,
675
- RUNNING,
676
- SUSPENDED,
677
- STOPPED
678
- };
679
-
680
- pthread_t pthread_id;
681
- native_thread_id_t native_tid;
682
- State state;
683
-
684
- TimeStamp state_changed_at;
685
- TimeStamp started_at;
686
- TimeStamp stopped_at;
687
-
688
- RawSample stack_on_suspend;
689
-
690
- std::string name;
691
-
692
- Thread(State state) : state(state) {
693
- pthread_id = pthread_self();
694
- native_tid = get_native_thread_id();
695
- started_at = state_changed_at = TimeStamp::Now();
696
- }
697
-
698
- void set_state(State new_state) {
699
- if (state == Thread::State::STOPPED) {
700
- return;
701
- }
702
-
703
- auto now = TimeStamp::Now();
704
-
705
- state = new_state;
706
- state_changed_at = now;
707
- if (new_state == State::STARTED) {
708
- if (started_at.zero()) {
709
- started_at = now;
710
- }
711
- } else if (new_state == State::STOPPED) {
712
- stopped_at = now;
1130
+ void install() {
1131
+ const std::lock_guard<std::mutex> lock(mutex);
1132
+ count++;
713
1133
 
714
- capture_name();
715
- }
1134
+ if (count == 1) setup_signal_handler();
716
1135
  }
717
1136
 
718
- bool running() {
719
- return state != State::STOPPED;
720
- }
1137
+ void uninstall() {
1138
+ const std::lock_guard<std::mutex> lock(mutex);
1139
+ count--;
721
1140
 
722
- void capture_name() {
723
- char buf[128];
724
- int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
725
- if (rc == 0)
726
- name = std::string(buf);
1141
+ if (count == 0) clear_signal_handler();
727
1142
  }
728
- };
729
-
730
- class Marker {
731
- public:
732
- enum Type {
733
- MARKER_GVL_THREAD_STARTED,
734
- MARKER_GVL_THREAD_READY,
735
- MARKER_GVL_THREAD_RESUMED,
736
- MARKER_GVL_THREAD_SUSPENDED,
737
- MARKER_GVL_THREAD_EXITED,
738
-
739
- MARKER_GC_START,
740
- MARKER_GC_END_MARK,
741
- MARKER_GC_END_SWEEP,
742
- MARKER_GC_ENTER,
743
- MARKER_GC_EXIT,
744
1143
 
745
- MARKER_MAX,
746
- };
747
- Type type;
748
- TimeStamp timestamp;
749
- native_thread_id_t thread_id;
750
- };
751
-
752
- class MarkerTable {
753
- public:
754
- std::vector<Marker> list;
755
- std::mutex mutex;
756
-
757
- void record(Marker::Type type) {
1144
+ void record_sample(LiveSample &sample, pthread_t pthread_id) {
758
1145
  const std::lock_guard<std::mutex> lock(mutex);
759
1146
 
760
- list.push_back({ type, TimeStamp::Now(), Thread::get_native_thread_id() });
1147
+ live_sample = &sample;
1148
+ if (pthread_kill(pthread_id, SIGPROF)) {
1149
+ rb_bug("pthread_kill failed");
1150
+ }
1151
+ sample.wait();
1152
+ live_sample = NULL;
761
1153
  }
762
- };
763
-
764
- extern "C" int ruby_thread_has_gvl_p(void);
765
1154
 
766
- class ThreadTable {
767
- public:
768
- std::vector<Thread> list;
1155
+ private:
769
1156
  std::mutex mutex;
1157
+ int count;
770
1158
 
771
- void started() {
772
- //const std::lock_guard<std::mutex> lock(mutex);
773
-
774
- //list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
775
- set_state(Thread::State::STARTED);
1159
+ static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
1160
+ assert(live_sample);
1161
+ live_sample->sample_current_thread();
776
1162
  }
777
1163
 
778
- void set_state(Thread::State new_state) {
779
- const std::lock_guard<std::mutex> lock(mutex);
780
-
781
- pthread_t current_thread = pthread_self();
782
- //cerr << "set state=" << new_state << " thread=" << gettid() << endl;
783
-
784
- for (auto &thread : list) {
785
- if (pthread_equal(current_thread, thread.pthread_id)) {
786
- thread.set_state(new_state);
787
-
788
- if (new_state == Thread::State::SUSPENDED) {
789
- thread.stack_on_suspend.sample();
790
- //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
791
- }
792
- return;
793
- }
794
- }
795
-
796
- pid_t native_tid = Thread::get_native_thread_id();
797
- list.emplace_back(new_state);
1164
+ void setup_signal_handler() {
1165
+ struct sigaction sa;
1166
+ sa.sa_sigaction = signal_handler;
1167
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
1168
+ sigemptyset(&sa.sa_mask);
1169
+ sigaction(SIGPROF, &sa, NULL);
798
1170
  }
799
- };
800
1171
 
801
- enum Category{
802
- CATEGORY_NORMAL,
803
- CATEGORY_IDLE
1172
+ void clear_signal_handler() {
1173
+ struct sigaction sa;
1174
+ sa.sa_handler = SIG_IGN;
1175
+ sa.sa_flags = SA_RESTART;
1176
+ sigemptyset(&sa.sa_mask);
1177
+ sigaction(SIGPROF, &sa, NULL);
1178
+ }
804
1179
  };
1180
+ LiveSample *GlobalSignalHandler::live_sample;
805
1181
 
806
1182
  class TimeCollector : public BaseCollector {
807
- std::vector<int> samples;
808
- std::vector<TimeStamp> timestamps;
809
- std::vector<native_thread_id_t> sample_threads;
810
- std::vector<Category> sample_categories;
811
-
812
1183
  MarkerTable markers;
813
1184
  ThreadTable threads;
814
1185
 
@@ -817,41 +1188,31 @@ class TimeCollector : public BaseCollector {
817
1188
  atomic_bool running;
818
1189
  SamplerSemaphore thread_stopped;
819
1190
 
820
- static inline LiveSample *live_sample;
821
-
822
- TimeStamp started_at;
823
1191
  TimeStamp interval;
824
1192
 
825
1193
  public:
826
- TimeCollector(TimeStamp interval) : interval(interval) {
1194
+ TimeCollector(TimeStamp interval) : interval(interval), threads(frame_list) {
827
1195
  }
828
1196
 
829
1197
  private:
830
1198
 
831
- void record_sample(const RawSample &sample, TimeStamp time, const Thread &thread, Category category) {
1199
+ void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
832
1200
  if (!sample.empty()) {
833
- int stack_index = frame_list.stack_index(sample);
834
- samples.push_back(stack_index);
835
- timestamps.push_back(time);
836
- sample_threads.push_back(thread.native_tid);
837
- sample_categories.push_back(category);
1201
+ int stack_index = thread.translator.translate(frame_list, sample);
1202
+ thread.samples.record_sample(
1203
+ stack_index,
1204
+ time,
1205
+ thread.native_tid,
1206
+ category
1207
+ );
838
1208
  }
839
1209
  }
840
1210
 
841
- static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
842
- assert(live_sample);
843
- live_sample->sample_current_thread();
844
- }
845
-
846
1211
  VALUE get_markers() {
847
- VALUE list = rb_ary_new();
1212
+ VALUE list = rb_ary_new2(this->markers.list.size());
848
1213
 
849
1214
  for (auto& marker: this->markers.list) {
850
- VALUE record[3] = {0};
851
- record[0] = ULL2NUM(marker.thread_id);
852
- record[1] = INT2NUM(marker.type);
853
- record[2] = ULL2NUM(marker.timestamp.nanoseconds());
854
- rb_ary_push(list, rb_ary_new_from_values(3, record));
1215
+ rb_ary_push(list, marker.to_array());
855
1216
  }
856
1217
 
857
1218
  return list;
@@ -859,20 +1220,16 @@ class TimeCollector : public BaseCollector {
859
1220
 
860
1221
  void sample_thread_run() {
861
1222
  LiveSample sample;
862
- live_sample = &sample;
863
1223
 
864
1224
  TimeStamp next_sample_schedule = TimeStamp::Now();
865
1225
  while (running) {
866
1226
  TimeStamp sample_start = TimeStamp::Now();
867
1227
 
868
1228
  threads.mutex.lock();
869
- for (auto thread : threads.list) {
1229
+ for (auto &thread : threads.list) {
870
1230
  //if (thread.state == Thread::State::RUNNING) {
871
- if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend.size() == 0)) {
872
- if (pthread_kill(thread.pthread_id, SIGPROF)) {
873
- rb_bug("pthread_kill failed");
874
- }
875
- sample.wait();
1231
+ if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
1232
+ GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
876
1233
 
877
1234
  if (sample.sample.gc) {
878
1235
  // fprintf(stderr, "skipping GC sample\n");
@@ -880,10 +1237,15 @@ class TimeCollector : public BaseCollector {
880
1237
  record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
881
1238
  }
882
1239
  } else if (thread.state == Thread::State::SUSPENDED) {
883
- record_sample(thread.stack_on_suspend, sample_start, thread, CATEGORY_IDLE);
1240
+ thread.samples.record_sample(
1241
+ thread.stack_on_suspend_idx,
1242
+ sample_start,
1243
+ thread.native_tid,
1244
+ CATEGORY_IDLE);
884
1245
  } else {
885
1246
  }
886
1247
  }
1248
+
887
1249
  threads.mutex.unlock();
888
1250
 
889
1251
  TimeStamp sample_complete = TimeStamp::Now();
@@ -899,8 +1261,6 @@ class TimeCollector : public BaseCollector {
899
1261
  TimeStamp::Sleep(sleep_time);
900
1262
  }
901
1263
 
902
- live_sample = NULL;
903
-
904
1264
  thread_stopped.post();
905
1265
  }
906
1266
 
@@ -910,10 +1270,21 @@ class TimeCollector : public BaseCollector {
910
1270
  return NULL;
911
1271
  }
912
1272
 
913
- static void internal_gc_event_cb(VALUE tpval, void *data) {
914
- TimeCollector *collector = static_cast<TimeCollector *>(data);
915
- rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
916
- int event = rb_tracearg_event_flag(tparg);
1273
+ static void internal_thread_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
1274
+ TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
1275
+
1276
+ switch (event) {
1277
+ case RUBY_EVENT_THREAD_BEGIN:
1278
+ collector->threads.started(&collector->markers);
1279
+ break;
1280
+ case RUBY_EVENT_THREAD_END:
1281
+ collector->threads.stopped(&collector->markers);
1282
+ break;
1283
+ }
1284
+ }
1285
+
1286
+ static void internal_gc_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
1287
+ TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
917
1288
 
918
1289
  switch (event) {
919
1290
  case RUBY_INTERNAL_EVENT_GC_START:
@@ -926,10 +1297,10 @@ class TimeCollector : public BaseCollector {
926
1297
  collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
927
1298
  break;
928
1299
  case RUBY_INTERNAL_EVENT_GC_ENTER:
929
- collector->markers.record(Marker::Type::MARKER_GC_ENTER);
1300
+ collector->markers.record_gc_entered();
930
1301
  break;
931
1302
  case RUBY_INTERNAL_EVENT_GC_EXIT:
932
- collector->markers.record(Marker::Type::MARKER_GC_EXIT);
1303
+ collector->markers.record_gc_leave();
933
1304
  break;
934
1305
  }
935
1306
  }
@@ -939,44 +1310,27 @@ class TimeCollector : public BaseCollector {
939
1310
  //cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
940
1311
 
941
1312
  switch (event) {
942
- case RUBY_INTERNAL_THREAD_EVENT_STARTED:
943
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_STARTED);
944
- collector->threads.started();
945
- break;
946
1313
  case RUBY_INTERNAL_THREAD_EVENT_READY:
947
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_READY);
1314
+ collector->threads.ready(&collector->markers);
948
1315
  break;
949
1316
  case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
950
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_RESUMED);
951
- collector->threads.set_state(Thread::State::RUNNING);
1317
+ collector->threads.resumed(&collector->markers);
952
1318
  break;
953
1319
  case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
954
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_SUSPENDED);
955
- collector->threads.set_state(Thread::State::SUSPENDED);
956
- break;
957
- case RUBY_INTERNAL_THREAD_EVENT_EXITED:
958
- collector->markers.record(Marker::Type::MARKER_GVL_THREAD_EXITED);
959
- collector->threads.set_state(Thread::State::STOPPED);
1320
+ collector->threads.suspended(&collector->markers);
960
1321
  break;
961
1322
 
962
1323
  }
963
1324
  }
964
1325
 
965
1326
  rb_internal_thread_event_hook_t *thread_hook;
966
- VALUE gc_hook;
967
1327
 
968
1328
  bool start() {
969
1329
  if (!BaseCollector::start()) {
970
1330
  return false;
971
1331
  }
972
1332
 
973
- started_at = TimeStamp::Now();
974
-
975
- struct sigaction sa;
976
- sa.sa_sigaction = signal_handler;
977
- sa.sa_flags = SA_RESTART | SA_SIGINFO;
978
- sigemptyset(&sa.sa_mask);
979
- sigaction(SIGPROF, &sa, NULL);
1333
+ GlobalSignalHandler::get_instance()->install();
980
1334
 
981
1335
  running = true;
982
1336
 
@@ -986,15 +1340,16 @@ class TimeCollector : public BaseCollector {
986
1340
  rb_bug("pthread_create");
987
1341
  }
988
1342
 
989
- // Set the state of the current Ruby thread to RUNNING.
990
- // We want to have at least one thread in our thread list because it's
991
- // possible that the profile might be such that we don't get any
992
- // thread switch events and we need at least one
993
- this->threads.set_state(Thread::State::RUNNING);
1343
+ // Set the state of the current Ruby thread to RUNNING, which we know it
1344
+ // is as it must have held the GVL to start the collector. We want to
1345
+ // have at least one thread in our thread list because it's possible
1346
+ // that the profile might be such that we don't get any thread switch
1347
+ // events and we need at least one
1348
+ this->threads.resumed(&this->markers);
994
1349
 
995
1350
  thread_hook = rb_internal_thread_add_event_hook(internal_thread_event_cb, RUBY_INTERNAL_THREAD_EVENT_MASK, this);
996
- gc_hook = rb_tracepoint_new(0, RUBY_GC_PHASE_EVENTS, internal_gc_event_cb, (void *)this);
997
- rb_tracepoint_enable(gc_hook);
1351
+ rb_add_event_hook(internal_gc_event_cb, RUBY_INTERNAL_EVENTS, PTR2NUM((void *)this));
1352
+ rb_add_event_hook(internal_thread_event_cb, RUBY_NORMAL_EVENTS, PTR2NUM((void *)this));
998
1353
 
999
1354
  return true;
1000
1355
  }
@@ -1005,14 +1360,11 @@ class TimeCollector : public BaseCollector {
1005
1360
  running = false;
1006
1361
  thread_stopped.wait();
1007
1362
 
1008
- struct sigaction sa;
1009
- sa.sa_handler = SIG_IGN;
1010
- sa.sa_flags = SA_RESTART;
1011
- sigemptyset(&sa.sa_mask);
1012
- sigaction(SIGPROF, &sa, NULL);
1363
+ GlobalSignalHandler::get_instance()->uninstall();
1013
1364
 
1014
1365
  rb_internal_thread_remove_event_hook(thread_hook);
1015
- rb_tracepoint_disable(gc_hook);
1366
+ rb_remove_event_hook(internal_gc_event_cb);
1367
+ rb_remove_event_hook(internal_thread_event_cb);
1016
1368
 
1017
1369
  // capture thread names
1018
1370
  for (auto& thread: this->threads.list) {
@@ -1031,45 +1383,15 @@ class TimeCollector : public BaseCollector {
1031
1383
  }
1032
1384
 
1033
1385
  VALUE build_collector_result() {
1034
- VALUE result = rb_obj_alloc(rb_cVernierResult);
1035
-
1036
- VALUE meta = rb_hash_new();
1037
- rb_ivar_set(result, rb_intern("@meta"), meta);
1038
- rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
1039
-
1040
- VALUE samples = rb_ary_new();
1041
- rb_ivar_set(result, rb_intern("@samples"), samples);
1042
- VALUE weights = rb_ary_new();
1043
- rb_ivar_set(result, rb_intern("@weights"), weights);
1044
- for (auto& stack_index: this->samples) {
1045
- rb_ary_push(samples, INT2NUM(stack_index));
1046
- rb_ary_push(weights, INT2NUM(1));
1047
- }
1048
-
1049
- VALUE timestamps = rb_ary_new();
1050
- rb_ivar_set(result, rb_intern("@timestamps"), timestamps);
1051
-
1052
- for (auto& timestamp: this->timestamps) {
1053
- rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
1054
- }
1055
-
1056
- VALUE sample_threads = rb_ary_new();
1057
- rb_ivar_set(result, rb_intern("@sample_threads"), sample_threads);
1058
- for (auto& thread: this->sample_threads) {
1059
- rb_ary_push(sample_threads, ULL2NUM(thread));
1060
- }
1061
-
1062
- VALUE sample_categories = rb_ary_new();
1063
- rb_ivar_set(result, rb_intern("@sample_categories"), sample_categories);
1064
- for (auto& cat: this->sample_categories) {
1065
- rb_ary_push(sample_categories, INT2NUM(cat));
1066
- }
1386
+ VALUE result = BaseCollector::build_collector_result();
1067
1387
 
1068
1388
  VALUE threads = rb_hash_new();
1069
1389
  rb_ivar_set(result, rb_intern("@threads"), threads);
1070
1390
 
1071
1391
  for (const auto& thread: this->threads.list) {
1072
1392
  VALUE hash = rb_hash_new();
1393
+ thread.samples.write_result(hash);
1394
+
1073
1395
  rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
1074
1396
  rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
1075
1397
  rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
@@ -1087,7 +1409,6 @@ class TimeCollector : public BaseCollector {
1087
1409
 
1088
1410
  void mark() {
1089
1411
  frame_list.mark_frames();
1090
- rb_gc_mark(gc_hook);
1091
1412
 
1092
1413
  //for (int i = 0; i < queued_length; i++) {
1093
1414
  // rb_gc_mark(queued_frames[i]);
@@ -1182,14 +1503,11 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
1182
1503
  }
1183
1504
 
1184
1505
  static void
1185
- Init_consts() {
1506
+ Init_consts(VALUE rb_mVernierMarkerPhase) {
1186
1507
  #define MARKER_CONST(name) \
1187
1508
  rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
1188
1509
 
1189
1510
  MARKER_CONST(GVL_THREAD_STARTED);
1190
- MARKER_CONST(GVL_THREAD_READY);
1191
- MARKER_CONST(GVL_THREAD_RESUMED);
1192
- MARKER_CONST(GVL_THREAD_SUSPENDED);
1193
1511
  MARKER_CONST(GVL_THREAD_EXITED);
1194
1512
 
1195
1513
  MARKER_CONST(GC_START);
@@ -1197,8 +1515,22 @@ Init_consts() {
1197
1515
  MARKER_CONST(GC_END_SWEEP);
1198
1516
  MARKER_CONST(GC_ENTER);
1199
1517
  MARKER_CONST(GC_EXIT);
1518
+ MARKER_CONST(GC_PAUSE);
1519
+
1520
+ MARKER_CONST(THREAD_RUNNING);
1521
+ MARKER_CONST(THREAD_STALLED);
1522
+ MARKER_CONST(THREAD_SUSPENDED);
1200
1523
 
1201
1524
  #undef MARKER_CONST
1525
+
1526
+ #define PHASE_CONST(name) \
1527
+ rb_define_const(rb_mVernierMarkerPhase, #name, INT2NUM(Marker::Phase::name))
1528
+
1529
+ PHASE_CONST(INSTANT);
1530
+ PHASE_CONST(INTERVAL);
1531
+ PHASE_CONST(INTERVAL_START);
1532
+ PHASE_CONST(INTERVAL_END);
1533
+ #undef PHASE_CONST
1202
1534
  }
1203
1535
 
1204
1536
  extern "C" void
@@ -1207,6 +1539,7 @@ Init_vernier(void)
1207
1539
  rb_mVernier = rb_define_module("Vernier");
1208
1540
  rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
1209
1541
  VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
1542
+ VALUE rb_mVernierMarkerPhase = rb_define_module_under(rb_mVernierMarker, "Phase");
1210
1543
  rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
1211
1544
 
1212
1545
  rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
@@ -1217,7 +1550,7 @@ Init_vernier(void)
1217
1550
  rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
1218
1551
  rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
1219
1552
 
1220
- Init_consts();
1553
+ Init_consts(rb_mVernierMarkerPhase);
1221
1554
 
1222
1555
  //static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
1223
1556
  //rb_global_variable(&gc_hook);