vernier 0.2.0 → 0.3.0

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