vernier 0.1.1 → 0.2.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.
@@ -1,27 +1,276 @@
1
1
  #include <iostream>
2
+ #include <iomanip>
2
3
  #include <vector>
3
4
  #include <memory>
4
5
  #include <algorithm>
5
6
  #include <sstream>
6
7
  #include <unordered_map>
7
8
  #include <unordered_set>
9
+ #include <cassert>
10
+ #include <atomic>
11
+ #include <mutex>
12
+ #include <optional>
13
+
14
+ #include <sys/time.h>
15
+ #include <signal.h>
16
+ #ifdef __APPLE__
17
+ #include <dispatch/dispatch.h>
18
+ #else
19
+ #include <semaphore.h>
20
+ #endif
8
21
 
9
22
  #include "vernier.hh"
10
23
  #include "stack.hh"
24
+
25
+ #include "ruby/ruby.h"
11
26
  #include "ruby/debug.h"
27
+ #include "ruby/thread.h"
12
28
 
13
- using namespace std;
29
+ // GC event's we'll monitor during profiling
30
+ #define RUBY_GC_PHASE_EVENTS \
31
+ RUBY_INTERNAL_EVENT_GC_START | \
32
+ RUBY_INTERNAL_EVENT_GC_END_MARK | \
33
+ RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
34
+ RUBY_INTERNAL_EVENT_GC_ENTER | \
35
+ RUBY_INTERNAL_EVENT_GC_EXIT
36
+
37
+ #define sym(name) ID2SYM(rb_intern_const(name))
38
+
39
+ // HACK: This isn't public, but the objspace ext uses it
40
+ extern "C" size_t rb_obj_memsize_of(VALUE);
14
41
 
15
- #define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
42
+ using namespace std;
16
43
 
17
44
  static VALUE rb_mVernier;
45
+ static VALUE rb_cVernierResult;
46
+ static VALUE rb_mVernierMarkerType;
47
+ static VALUE rb_cVernierCollector;
48
+
49
+ class TimeStamp {
50
+ static const uint64_t nanoseconds_per_second = 1000000000;
51
+ uint64_t value_ns;
52
+
53
+ TimeStamp(uint64_t value_ns) : value_ns(value_ns) {}
54
+
55
+ public:
56
+ TimeStamp() : value_ns(0) {}
57
+
58
+ static TimeStamp Now() {
59
+ struct timespec ts;
60
+ clock_gettime(CLOCK_MONOTONIC, &ts);
61
+ return TimeStamp(ts.tv_sec * nanoseconds_per_second + ts.tv_nsec);
62
+ }
63
+
64
+ static TimeStamp Zero() {
65
+ return TimeStamp(0);
66
+ }
67
+
68
+ static void Sleep(const TimeStamp &time) {
69
+ struct timespec ts = time.timespec();
70
+
71
+ int res;
72
+ do {
73
+ res = nanosleep(&ts, &ts);
74
+ } while (res && errno == EINTR);
75
+ }
76
+
77
+ static TimeStamp from_microseconds(uint64_t us) {
78
+ return TimeStamp(us * 1000);
79
+ }
80
+
81
+ static TimeStamp from_nanoseconds(uint64_t ns) {
82
+ return TimeStamp(ns);
83
+ }
84
+
85
+ TimeStamp operator-(const TimeStamp &other) const {
86
+ TimeStamp result = *this;
87
+ return result -= other;
88
+ }
89
+
90
+ TimeStamp &operator-=(const TimeStamp &other) {
91
+ if (value_ns > other.value_ns) {
92
+ value_ns = value_ns - other.value_ns;
93
+ } else {
94
+ // underflow
95
+ value_ns = 0;
96
+ }
97
+ return *this;
98
+ }
99
+
100
+ TimeStamp operator+(const TimeStamp &other) const {
101
+ TimeStamp result = *this;
102
+ return result += other;
103
+ }
104
+
105
+ TimeStamp &operator+=(const TimeStamp &other) {
106
+ uint64_t new_value = value_ns + other.value_ns;
107
+ value_ns = new_value;
108
+ return *this;
109
+ }
110
+
111
+ bool operator<(const TimeStamp &other) const {
112
+ return value_ns < other.value_ns;
113
+ }
114
+
115
+ bool operator<=(const TimeStamp &other) const {
116
+ return value_ns <= other.value_ns;
117
+ }
118
+
119
+ bool operator>(const TimeStamp &other) const {
120
+ return value_ns > other.value_ns;
121
+ }
122
+
123
+ bool operator>=(const TimeStamp &other) const {
124
+ return value_ns >= other.value_ns;
125
+ }
126
+
127
+ uint64_t nanoseconds() const {
128
+ return value_ns;
129
+ }
130
+
131
+ uint64_t microseconds() const {
132
+ return value_ns / 1000;
133
+ }
134
+
135
+ bool zero() const {
136
+ return value_ns == 0;
137
+ }
138
+
139
+ struct timespec timespec() const {
140
+ struct timespec ts;
141
+ ts.tv_sec = nanoseconds() / nanoseconds_per_second;
142
+ ts.tv_nsec = (nanoseconds() % nanoseconds_per_second);
143
+ return ts;
144
+ }
145
+ };
146
+
147
+ std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
148
+ os << info.nanoseconds() << "ns";
149
+ return os;
150
+ }
18
151
 
19
- struct retained_collector {
20
- int allocated_objects = 0;
21
- int freed_objects = 0;
152
+ // A basic semaphore built on sem_wait/sem_post
153
+ // post() is guaranteed to be async-signal-safe
154
+ class SamplerSemaphore {
155
+ #ifdef __APPLE__
156
+ dispatch_semaphore_t sem;
157
+ #else
158
+ sem_t sem;
159
+ #endif
22
160
 
23
- std::unordered_set<VALUE> unique_frames;
24
- std::unordered_map<VALUE, std::unique_ptr<Stack>> object_frames;
161
+ public:
162
+
163
+ SamplerSemaphore(unsigned int value = 0) {
164
+ #ifdef __APPLE__
165
+ sem = dispatch_semaphore_create(value);
166
+ #else
167
+ sem_init(&sem, 0, value);
168
+ #endif
169
+ };
170
+
171
+ ~SamplerSemaphore() {
172
+ #ifdef __APPLE__
173
+ dispatch_release(sem);
174
+ #else
175
+ sem_destroy(&sem);
176
+ #endif
177
+ };
178
+
179
+ void wait() {
180
+ #ifdef __APPLE__
181
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
182
+ #else
183
+ int ret;
184
+ do {
185
+ ret = sem_wait(&sem);
186
+ } while (ret && errno == EINTR);
187
+ assert(ret == 0);
188
+ #endif
189
+ }
190
+
191
+ void post() {
192
+ #ifdef __APPLE__
193
+ dispatch_semaphore_signal(sem);
194
+ #else
195
+ sem_post(&sem);
196
+ #endif
197
+ }
198
+ };
199
+
200
+ struct RawSample {
201
+ constexpr static int MAX_LEN = 2048;
202
+ VALUE frames[MAX_LEN];
203
+ int lines[MAX_LEN];
204
+ int len;
205
+ bool gc;
206
+
207
+ RawSample() : len(0), gc(false) { }
208
+
209
+ int size() const {
210
+ return len;
211
+ }
212
+
213
+ Frame frame(int i) const {
214
+ const Frame frame = {frames[i], lines[i]};
215
+ return frame;
216
+ }
217
+
218
+ void sample() {
219
+ if (!ruby_native_thread_p()) {
220
+ clear();
221
+ return;
222
+ }
223
+
224
+ if (rb_during_gc()) {
225
+ gc = true;
226
+ len = 0;
227
+ } else {
228
+ gc = false;
229
+ len = rb_profile_frames(0, MAX_LEN, frames, lines);
230
+ }
231
+ }
232
+
233
+ void clear() {
234
+ len = 0;
235
+ gc = false;
236
+ }
237
+
238
+ bool empty() const {
239
+ return len == 0;
240
+ }
241
+ };
242
+
243
+ // Based very loosely on the design of Gecko's SigHandlerCoordinator
244
+ // This is used for communication between the profiler thread and the signal
245
+ // handlers in the observed thread.
246
+ struct LiveSample {
247
+ RawSample sample;
248
+
249
+ SamplerSemaphore sem_complete;
250
+
251
+ // Wait for a sample to be collected by the signal handler on another thread
252
+ void wait() {
253
+ sem_complete.wait();
254
+ }
255
+
256
+ int size() const {
257
+ return sample.size();
258
+ }
259
+
260
+ Frame frame(int i) const {
261
+ return sample.frame(i);
262
+ }
263
+
264
+ // Called from a signal handler in the observed thread in order to take a
265
+ // sample and signal to the proifiler thread that the sample is ready.
266
+ //
267
+ // CRuby doesn't guarantee that rb_profile_frames can be used as
268
+ // async-signal-safe but in practice it seems to be.
269
+ // sem_post is safe in an async-signal-safe context.
270
+ void sample_current_thread() {
271
+ sample.sample();
272
+ sem_complete.post();
273
+ }
25
274
  };
26
275
 
27
276
  struct TraceArg {
@@ -42,149 +291,934 @@ struct TraceArg {
42
291
  }
43
292
  };
44
293
 
45
- static retained_collector _collector;
294
+ struct FrameList {
295
+ std::unordered_map<std::string, int> string_to_idx;
296
+ std::vector<std::string> string_list;
46
297
 
47
- static VALUE tp_newobj;
48
- static VALUE tp_freeobj;
49
- static void
50
- newobj_i(VALUE tpval, void *data) {
51
- retained_collector *collector = static_cast<retained_collector *>(data);
52
- TraceArg tp(tpval);
53
- collector->allocated_objects++;
298
+ int string_index(const std::string str) {
299
+ auto it = string_to_idx.find(str);
300
+ if (it == string_to_idx.end()) {
301
+ int idx = string_list.size();
302
+ string_list.push_back(str);
54
303
 
55
- VALUE frames_buffer[2048];
56
- int lines_buffer[2048];
57
- int n = rb_profile_frames(0, 2048, frames_buffer, lines_buffer);
304
+ auto result = string_to_idx.insert({str, idx});
305
+ it = result.first;
306
+ }
58
307
 
59
- for (int i = 0; i < n; i++) {
60
- collector->unique_frames.insert(frames_buffer[i]);
308
+ return it->second;
61
309
  }
62
310
 
63
- collector->object_frames.emplace(
64
- tp.obj,
65
- make_unique<Stack>(frames_buffer, lines_buffer, n)
66
- );
67
- }
311
+ struct FrameWithInfo {
312
+ Frame frame;
313
+ FrameInfo info;
314
+ };
315
+
316
+ std::unordered_map<Frame, int> frame_to_idx;
317
+ std::vector<Frame> frame_list;
318
+ std::vector<FrameWithInfo> frame_with_info_list;
319
+ int frame_index(const Frame frame) {
320
+ auto it = frame_to_idx.find(frame);
321
+ if (it == frame_to_idx.end()) {
322
+ int idx = frame_list.size();
323
+ frame_list.push_back(frame);
324
+ auto result = frame_to_idx.insert({frame, idx});
325
+ it = result.first;
326
+ }
327
+ return it->second;
328
+ }
329
+
330
+ struct StackNode {
331
+ std::unordered_map<Frame, int> children;
332
+ Frame frame;
333
+ int parent;
334
+ int index;
335
+
336
+ StackNode(Frame frame, int index, int parent) : frame(frame), index(index), parent(parent) {}
337
+
338
+ // root
339
+ StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
340
+ };
341
+
342
+ StackNode root_stack_node;
343
+ vector<StackNode> stack_node_list;
344
+
345
+ int stack_index(const RawSample &stack) {
346
+ if (stack.empty()) {
347
+ throw std::runtime_error("empty stack");
348
+ }
349
+
350
+ StackNode *node = &root_stack_node;
351
+ for (int i = stack.size() - 1; i >= 0; i--) {
352
+ const Frame &frame = stack.frame(i);
353
+ node = next_stack_node(node, frame);
354
+ }
355
+ return node->index;
356
+ }
357
+
358
+ StackNode *next_stack_node(StackNode *node, const Frame &frame) {
359
+ int next_node_idx = node->children[frame];
360
+ if (next_node_idx == 0) {
361
+ // insert a new node
362
+ next_node_idx = stack_node_list.size();
363
+ node->children[frame] = next_node_idx;
364
+ stack_node_list.emplace_back(
365
+ frame,
366
+ next_node_idx,
367
+ node->index
368
+ );
369
+ }
370
+
371
+ return &stack_node_list[next_node_idx];
372
+ }
373
+
374
+ // Converts Frames from stacks other tables. "Symbolicates" the frames
375
+ // which allocates.
376
+ void finalize() {
377
+ for (const auto &stack_node : stack_node_list) {
378
+ frame_index(stack_node.frame);
379
+ }
380
+ for (const auto &frame : frame_list) {
381
+ frame_with_info_list.push_back(FrameWithInfo{frame, frame.info()});
382
+ }
383
+ }
384
+
385
+ void mark_frames() {
386
+ for (auto stack_node: stack_node_list) {
387
+ rb_gc_mark(stack_node.frame.frame);
388
+ }
389
+ }
390
+
391
+ void clear() {
392
+ string_list.clear();
393
+ frame_list.clear();
394
+ stack_node_list.clear();
395
+ frame_with_info_list.clear();
396
+
397
+ string_to_idx.clear();
398
+ frame_to_idx.clear();
399
+ root_stack_node.children.clear();
400
+ }
401
+
402
+ void write_result(VALUE result) {
403
+ FrameList &frame_list = *this;
404
+
405
+ VALUE stack_table = rb_hash_new();
406
+ rb_ivar_set(result, rb_intern("@stack_table"), stack_table);
407
+ VALUE stack_table_parent = rb_ary_new();
408
+ VALUE stack_table_frame = rb_ary_new();
409
+ rb_hash_aset(stack_table, sym("parent"), stack_table_parent);
410
+ rb_hash_aset(stack_table, sym("frame"), stack_table_frame);
411
+ for (const auto &stack : frame_list.stack_node_list) {
412
+ VALUE parent_val = stack.parent == -1 ? Qnil : INT2NUM(stack.parent);
413
+ rb_ary_push(stack_table_parent, parent_val);
414
+ rb_ary_push(stack_table_frame, INT2NUM(frame_list.frame_index(stack.frame)));
415
+ }
416
+
417
+ VALUE frame_table = rb_hash_new();
418
+ rb_ivar_set(result, rb_intern("@frame_table"), frame_table);
419
+ VALUE frame_table_func = rb_ary_new();
420
+ VALUE frame_table_line = rb_ary_new();
421
+ rb_hash_aset(frame_table, sym("func"), frame_table_func);
422
+ rb_hash_aset(frame_table, sym("line"), frame_table_line);
423
+ //for (const auto &frame : frame_list.frame_list) {
424
+ for (int i = 0; i < frame_list.frame_with_info_list.size(); i++) {
425
+ const auto &frame = frame_list.frame_with_info_list[i];
426
+ rb_ary_push(frame_table_func, INT2NUM(i));
427
+ rb_ary_push(frame_table_line, INT2NUM(frame.frame.line));
428
+ }
429
+
430
+ // TODO: dedup funcs before this step
431
+ VALUE func_table = rb_hash_new();
432
+ rb_ivar_set(result, rb_intern("@func_table"), func_table);
433
+ VALUE func_table_name = rb_ary_new();
434
+ VALUE func_table_filename = rb_ary_new();
435
+ VALUE func_table_first_line = rb_ary_new();
436
+ rb_hash_aset(func_table, sym("name"), func_table_name);
437
+ rb_hash_aset(func_table, sym("filename"), func_table_filename);
438
+ rb_hash_aset(func_table, sym("first_line"), func_table_first_line);
439
+ for (const auto &frame : frame_list.frame_with_info_list) {
440
+ const std::string label = frame.info.label;
441
+ const std::string filename = frame.info.file;
442
+ const int first_line = frame.info.first_lineno;
443
+
444
+ rb_ary_push(func_table_name, rb_str_new(label.c_str(), label.length()));
445
+ rb_ary_push(func_table_filename, rb_str_new(filename.c_str(), filename.length()));
446
+ rb_ary_push(func_table_first_line, INT2NUM(first_line));
447
+ }
448
+ }
449
+ };
450
+
451
+ class BaseCollector {
452
+ protected:
453
+
454
+ virtual void reset() {
455
+ frame_list.clear();
456
+ }
457
+
458
+ public:
459
+ bool running = false;
460
+ FrameList frame_list;
461
+
462
+ virtual bool start() {
463
+ if (running) {
464
+ return false;
465
+ } else {
466
+ running = true;
467
+ return true;
468
+ }
469
+ }
470
+
471
+ virtual VALUE stop() {
472
+ if (!running) {
473
+ rb_raise(rb_eRuntimeError, "collector not running");
474
+ }
475
+ running = false;
476
+
477
+ return Qnil;
478
+ }
479
+
480
+ virtual void sample() {
481
+ rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
482
+ };
483
+
484
+ virtual void mark() {
485
+ frame_list.mark_frames();
486
+ };
487
+
488
+ virtual VALUE get_markers() {
489
+ return rb_ary_new();
490
+ };
491
+ };
492
+
493
+ class CustomCollector : public BaseCollector {
494
+ std::vector<int> samples;
495
+
496
+ void sample() {
497
+ RawSample sample;
498
+ sample.sample();
499
+ int stack_index = frame_list.stack_index(sample);
500
+
501
+ samples.push_back(stack_index);
502
+ }
503
+
504
+ VALUE stop() {
505
+ BaseCollector::stop();
506
+
507
+ frame_list.finalize();
508
+
509
+ VALUE result = build_collector_result();
510
+
511
+ reset();
512
+
513
+ return result;
514
+ }
515
+
516
+ VALUE build_collector_result() {
517
+ VALUE result = rb_obj_alloc(rb_cVernierResult);
518
+
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);
523
+
524
+ for (auto& stack_index: this->samples) {
525
+ rb_ary_push(samples, INT2NUM(stack_index));
526
+ rb_ary_push(weights, INT2NUM(1));
527
+ }
528
+
529
+ frame_list.write_result(result);
530
+
531
+ return result;
532
+ }
533
+ };
534
+
535
+ class RetainedCollector : public BaseCollector {
536
+ void reset() {
537
+ object_frames.clear();
538
+ object_list.clear();
539
+
540
+ BaseCollector::reset();
541
+ }
542
+
543
+ void record(VALUE obj) {
544
+ RawSample sample;
545
+ sample.sample();
546
+ int stack_index = frame_list.stack_index(sample);
547
+
548
+ object_list.push_back(obj);
549
+ object_frames.emplace(obj, stack_index);
550
+ }
551
+
552
+ std::unordered_map<VALUE, int> object_frames;
553
+ std::vector<VALUE> object_list;
554
+
555
+ VALUE tp_newobj = Qnil;
556
+ VALUE tp_freeobj = Qnil;
557
+
558
+ static void newobj_i(VALUE tpval, void *data) {
559
+ RetainedCollector *collector = static_cast<RetainedCollector *>(data);
560
+ TraceArg tp(tpval);
561
+
562
+ collector->record(tp.obj);
563
+ }
564
+
565
+ static void freeobj_i(VALUE tpval, void *data) {
566
+ RetainedCollector *collector = static_cast<RetainedCollector *>(data);
567
+ TraceArg tp(tpval);
568
+
569
+ collector->object_frames.erase(tp.obj);
570
+ }
571
+
572
+ public:
573
+
574
+ bool start() {
575
+ if (!BaseCollector::start()) {
576
+ return false;
577
+ }
578
+
579
+ tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
580
+ tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, this);
581
+
582
+ rb_tracepoint_enable(tp_newobj);
583
+ rb_tracepoint_enable(tp_freeobj);
584
+
585
+ return true;
586
+ }
587
+
588
+ VALUE stop() {
589
+ BaseCollector::stop();
590
+
591
+ // GC before we start turning stacks into strings
592
+ rb_gc();
593
+
594
+ // Stop tracking any more new objects, but we'll continue tracking free'd
595
+ // objects as we may be able to free some as we remove our own references
596
+ // to stack frames.
597
+ rb_tracepoint_disable(tp_newobj);
598
+ tp_newobj = Qnil;
599
+
600
+ frame_list.finalize();
601
+
602
+ // We should have collected info for all our frames, so no need to continue
603
+ // marking them
604
+ // FIXME: previously here we cleared the list of frames so we would stop
605
+ // marking them. Maybe now we should set a flag so that we stop marking them
606
+
607
+ // GC again
608
+ rb_gc();
609
+
610
+ rb_tracepoint_disable(tp_freeobj);
611
+ tp_freeobj = Qnil;
612
+
613
+ VALUE result = build_collector_result();
614
+
615
+ reset();
616
+
617
+ return result;
618
+ }
619
+
620
+ VALUE build_collector_result() {
621
+ RetainedCollector *collector = this;
622
+ FrameList &frame_list = collector->frame_list;
623
+
624
+ VALUE result = rb_obj_alloc(rb_cVernierResult);
625
+
626
+ VALUE samples = rb_ary_new();
627
+ rb_ivar_set(result, rb_intern("@samples"), samples);
628
+ VALUE weights = rb_ary_new();
629
+ rb_ivar_set(result, rb_intern("@weights"), weights);
630
+
631
+ for (auto& obj: collector->object_list) {
632
+ const auto search = collector->object_frames.find(obj);
633
+ if (search != collector->object_frames.end()) {
634
+ int stack_index = search->second;
635
+
636
+ rb_ary_push(samples, INT2NUM(stack_index));
637
+ rb_ary_push(weights, INT2NUM(rb_obj_memsize_of(obj)));
638
+ }
639
+ }
640
+
641
+ frame_list.write_result(result);
642
+
643
+ return result;
644
+ }
645
+
646
+ void mark() {
647
+ // We don't mark the objects, but we MUST mark the frames, otherwise they
648
+ // can be garbage collected.
649
+ // When we stop collection we will stringify the remaining frames, and then
650
+ // clear them from the set, allowing them to be removed from out output.
651
+ frame_list.mark_frames();
652
+
653
+ rb_gc_mark(tp_newobj);
654
+ rb_gc_mark(tp_freeobj);
655
+ }
656
+ };
657
+
658
+ typedef uint64_t native_thread_id_t;
659
+
660
+ class Thread {
661
+ 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
671
+ }
672
+
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;
713
+
714
+ capture_name();
715
+ }
716
+ }
717
+
718
+ bool running() {
719
+ return state != State::STOPPED;
720
+ }
721
+
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);
727
+ }
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
+
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) {
758
+ const std::lock_guard<std::mutex> lock(mutex);
759
+
760
+ list.push_back({ type, TimeStamp::Now(), Thread::get_native_thread_id() });
761
+ }
762
+ };
763
+
764
+ extern "C" int ruby_thread_has_gvl_p(void);
765
+
766
+ class ThreadTable {
767
+ public:
768
+ std::vector<Thread> list;
769
+ std::mutex mutex;
770
+
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);
776
+ }
777
+
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);
798
+ }
799
+ };
800
+
801
+ enum Category{
802
+ CATEGORY_NORMAL,
803
+ CATEGORY_IDLE
804
+ };
805
+
806
+ 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
+ MarkerTable markers;
813
+ ThreadTable threads;
814
+
815
+ pthread_t sample_thread;
816
+
817
+ atomic_bool running;
818
+ SamplerSemaphore thread_stopped;
819
+
820
+ static inline LiveSample *live_sample;
821
+
822
+ TimeStamp started_at;
823
+ TimeStamp interval;
824
+
825
+ public:
826
+ TimeCollector(TimeStamp interval) : interval(interval) {
827
+ }
828
+
829
+ private:
830
+
831
+ void record_sample(const RawSample &sample, TimeStamp time, const Thread &thread, Category category) {
832
+ 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);
838
+ }
839
+ }
840
+
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
+ VALUE get_markers() {
847
+ VALUE list = rb_ary_new();
848
+
849
+ 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));
855
+ }
856
+
857
+ return list;
858
+ }
859
+
860
+ void sample_thread_run() {
861
+ LiveSample sample;
862
+ live_sample = &sample;
863
+
864
+ TimeStamp next_sample_schedule = TimeStamp::Now();
865
+ while (running) {
866
+ TimeStamp sample_start = TimeStamp::Now();
867
+
868
+ threads.mutex.lock();
869
+ for (auto thread : threads.list) {
870
+ //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();
876
+
877
+ if (sample.sample.gc) {
878
+ // fprintf(stderr, "skipping GC sample\n");
879
+ } else {
880
+ record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
881
+ }
882
+ } else if (thread.state == Thread::State::SUSPENDED) {
883
+ record_sample(thread.stack_on_suspend, sample_start, thread, CATEGORY_IDLE);
884
+ } else {
885
+ }
886
+ }
887
+ threads.mutex.unlock();
888
+
889
+ TimeStamp sample_complete = TimeStamp::Now();
890
+
891
+ next_sample_schedule += interval;
892
+
893
+ if (next_sample_schedule < sample_complete) {
894
+ //fprintf(stderr, "fell behind by %ius\n", (sample_complete - next_sample_schedule).microseconds());
895
+ next_sample_schedule = sample_complete + interval;
896
+ }
897
+
898
+ TimeStamp sleep_time = next_sample_schedule - sample_complete;
899
+ TimeStamp::Sleep(sleep_time);
900
+ }
901
+
902
+ live_sample = NULL;
903
+
904
+ thread_stopped.post();
905
+ }
906
+
907
+ static void *sample_thread_entry(void *arg) {
908
+ TimeCollector *collector = static_cast<TimeCollector *>(arg);
909
+ collector->sample_thread_run();
910
+ return NULL;
911
+ }
912
+
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);
917
+
918
+ switch (event) {
919
+ case RUBY_INTERNAL_EVENT_GC_START:
920
+ collector->markers.record(Marker::Type::MARKER_GC_START);
921
+ break;
922
+ case RUBY_INTERNAL_EVENT_GC_END_MARK:
923
+ collector->markers.record(Marker::Type::MARKER_GC_END_MARK);
924
+ break;
925
+ case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
926
+ collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
927
+ break;
928
+ case RUBY_INTERNAL_EVENT_GC_ENTER:
929
+ collector->markers.record(Marker::Type::MARKER_GC_ENTER);
930
+ break;
931
+ case RUBY_INTERNAL_EVENT_GC_EXIT:
932
+ collector->markers.record(Marker::Type::MARKER_GC_EXIT);
933
+ break;
934
+ }
935
+ }
936
+
937
+ static void internal_thread_event_cb(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *data) {
938
+ TimeCollector *collector = static_cast<TimeCollector *>(data);
939
+ //cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
940
+
941
+ 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
+ case RUBY_INTERNAL_THREAD_EVENT_READY:
947
+ collector->markers.record(Marker::Type::MARKER_GVL_THREAD_READY);
948
+ break;
949
+ case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
950
+ collector->markers.record(Marker::Type::MARKER_GVL_THREAD_RESUMED);
951
+ collector->threads.set_state(Thread::State::RUNNING);
952
+ break;
953
+ 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);
960
+ break;
961
+
962
+ }
963
+ }
964
+
965
+ rb_internal_thread_event_hook_t *thread_hook;
966
+ VALUE gc_hook;
967
+
968
+ bool start() {
969
+ if (!BaseCollector::start()) {
970
+ return false;
971
+ }
972
+
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);
980
+
981
+ running = true;
982
+
983
+ int ret = pthread_create(&sample_thread, NULL, &sample_thread_entry, this);
984
+ if (ret != 0) {
985
+ perror("pthread_create");
986
+ rb_bug("pthread_create");
987
+ }
988
+
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);
994
+
995
+ 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);
998
+
999
+ return true;
1000
+ }
1001
+
1002
+ VALUE stop() {
1003
+ BaseCollector::stop();
1004
+
1005
+ running = false;
1006
+ thread_stopped.wait();
1007
+
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);
1013
+
1014
+ rb_internal_thread_remove_event_hook(thread_hook);
1015
+ rb_tracepoint_disable(gc_hook);
1016
+
1017
+ // capture thread names
1018
+ for (auto& thread: this->threads.list) {
1019
+ if (thread.running()) {
1020
+ thread.capture_name();
1021
+ }
1022
+ }
1023
+
1024
+ frame_list.finalize();
1025
+
1026
+ VALUE result = build_collector_result();
1027
+
1028
+ reset();
1029
+
1030
+ return result;
1031
+ }
1032
+
1033
+ 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
+ }
1067
+
1068
+ VALUE threads = rb_hash_new();
1069
+ rb_ivar_set(result, rb_intern("@threads"), threads);
1070
+
1071
+ for (const auto& thread: this->threads.list) {
1072
+ VALUE hash = rb_hash_new();
1073
+ rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
1074
+ rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
1075
+ rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
1076
+ if (!thread.stopped_at.zero()) {
1077
+ rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread.stopped_at.nanoseconds()));
1078
+ }
1079
+ rb_hash_aset(hash, sym("name"), rb_str_new(thread.name.data(), thread.name.length()));
1080
+
1081
+ }
1082
+
1083
+ frame_list.write_result(result);
1084
+
1085
+ return result;
1086
+ }
1087
+
1088
+ void mark() {
1089
+ frame_list.mark_frames();
1090
+ rb_gc_mark(gc_hook);
1091
+
1092
+ //for (int i = 0; i < queued_length; i++) {
1093
+ // rb_gc_mark(queued_frames[i]);
1094
+ //}
1095
+
1096
+ // FIXME: How can we best mark buffered or pending frames?
1097
+ }
1098
+ };
68
1099
 
69
1100
  static void
70
- freeobj_i(VALUE tpval, void *data) {
71
- retained_collector *collector = static_cast<retained_collector *>(data);
72
- TraceArg tp(tpval);
73
- collector->freed_objects++;
1101
+ collector_mark(void *data) {
1102
+ BaseCollector *collector = static_cast<BaseCollector *>(data);
1103
+ collector->mark();
1104
+ }
74
1105
 
75
- collector->object_frames.erase(tp.obj);
1106
+ static void
1107
+ collector_free(void *data) {
1108
+ BaseCollector *collector = static_cast<BaseCollector *>(data);
1109
+ delete collector;
76
1110
  }
77
1111
 
1112
+ static const rb_data_type_t rb_collector_type = {
1113
+ .wrap_struct_name = "vernier/collector",
1114
+ .function = {
1115
+ //.dmemsize = rb_collector_memsize,
1116
+ .dmark = collector_mark,
1117
+ .dfree = collector_free,
1118
+ },
1119
+ };
78
1120
 
79
- static VALUE
80
- trace_retained_start(VALUE self) {
81
- retained_collector *collector = &_collector;
1121
+ static BaseCollector *get_collector(VALUE obj) {
1122
+ BaseCollector *collector;
1123
+ TypedData_Get_Struct(obj, BaseCollector, &rb_collector_type, collector);
1124
+ return collector;
1125
+ }
82
1126
 
83
- tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, collector);
84
- tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, collector);
1127
+ static VALUE
1128
+ collector_start(VALUE self) {
1129
+ auto *collector = get_collector(self);
85
1130
 
86
- rb_tracepoint_enable(tp_newobj);
87
- rb_tracepoint_enable(tp_freeobj);
1131
+ if (!collector->start()) {
1132
+ rb_raise(rb_eRuntimeError, "already running");
1133
+ }
88
1134
 
89
1135
  return Qtrue;
90
1136
  }
91
1137
 
92
- #define sym(name) ID2SYM(rb_intern_const(name))
93
-
94
- // HACK: This isn't public, but the objspace ext uses it
95
- extern "C" size_t rb_obj_memsize_of(VALUE);
1138
+ static VALUE
1139
+ collector_stop(VALUE self) {
1140
+ auto *collector = get_collector(self);
96
1141
 
97
- static const char *
98
- ruby_object_type_name(VALUE obj) {
99
- enum ruby_value_type type = rb_type(obj);
100
-
101
- #define TYPE_CASE(x) case (x): return (#x)
102
-
103
- // Many of these are impossible, but it's easier to just include them
104
- switch (type) {
105
- TYPE_CASE(T_OBJECT);
106
- TYPE_CASE(T_CLASS);
107
- TYPE_CASE(T_MODULE);
108
- TYPE_CASE(T_FLOAT);
109
- TYPE_CASE(T_STRING);
110
- TYPE_CASE(T_REGEXP);
111
- TYPE_CASE(T_ARRAY);
112
- TYPE_CASE(T_HASH);
113
- TYPE_CASE(T_STRUCT);
114
- TYPE_CASE(T_BIGNUM);
115
- TYPE_CASE(T_FILE);
116
- TYPE_CASE(T_DATA);
117
- TYPE_CASE(T_MATCH);
118
- TYPE_CASE(T_COMPLEX);
119
- TYPE_CASE(T_RATIONAL);
120
-
121
- TYPE_CASE(T_NIL);
122
- TYPE_CASE(T_TRUE);
123
- TYPE_CASE(T_FALSE);
124
- TYPE_CASE(T_SYMBOL);
125
- TYPE_CASE(T_FIXNUM);
126
- TYPE_CASE(T_UNDEF);
127
-
128
- TYPE_CASE(T_IMEMO);
129
- TYPE_CASE(T_NODE);
130
- TYPE_CASE(T_ICLASS);
131
- TYPE_CASE(T_ZOMBIE);
132
- TYPE_CASE(T_MOVED);
133
-
134
- default:
135
- return "unknown type";
136
- }
137
- #undef TYPE_CASE
1142
+ VALUE result = collector->stop();
1143
+ return result;
138
1144
  }
139
1145
 
140
1146
  static VALUE
141
- trace_retained_stop(VALUE self) {
142
- rb_tracepoint_disable(tp_newobj);
143
- rb_tracepoint_disable(tp_freeobj);
1147
+ markers(VALUE self) {
1148
+ auto *collector = get_collector(self);
144
1149
 
145
- retained_collector *collector = &_collector;
1150
+ return collector->get_markers();
1151
+ }
146
1152
 
147
- std::stringstream ss;
1153
+ static VALUE
1154
+ collector_sample(VALUE self) {
1155
+ auto *collector = get_collector(self);
148
1156
 
149
- for (auto& it: collector->object_frames) {
150
- VALUE obj = it.first;
151
- const Stack &stack = *it.second;
1157
+ collector->sample();
1158
+ return Qtrue;
1159
+ }
152
1160
 
153
- for (int i = stack.size() - 1; i >= 0; i--) {
154
- const Frame &frame = stack.frame(i);
155
- ss << frame;
156
- if (i > 0) ss << ";";
1161
+ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
1162
+ BaseCollector *collector;
1163
+ if (mode == sym("retained")) {
1164
+ collector = new RetainedCollector();
1165
+ } else if (mode == sym("custom")) {
1166
+ collector = new CustomCollector();
1167
+ } else if (mode == sym("wall")) {
1168
+ VALUE intervalv = rb_hash_aref(options, sym("interval"));
1169
+ TimeStamp interval;
1170
+ if (NIL_P(intervalv)) {
1171
+ interval = TimeStamp::from_microseconds(500);
1172
+ } else {
1173
+ interval = TimeStamp::from_microseconds(NUM2UINT(intervalv));
157
1174
  }
158
- ss << ";" << ruby_object_type_name(obj);
159
- ss << " " << rb_obj_memsize_of(obj) << endl;
1175
+ collector = new TimeCollector(interval);
1176
+ } else {
1177
+ rb_raise(rb_eArgError, "invalid mode");
160
1178
  }
161
-
162
- std::string s = ss.str();
163
- VALUE str = rb_str_new(s.c_str(), s.size());
164
-
165
- return str;
1179
+ VALUE obj = TypedData_Wrap_Struct(self, &rb_collector_type, collector);
1180
+ rb_funcall(obj, rb_intern("initialize"), 1, mode);
1181
+ return obj;
166
1182
  }
167
1183
 
168
1184
  static void
169
- retained_collector_mark(void *data) {
170
- retained_collector *collector = static_cast<retained_collector *>(data);
1185
+ Init_consts() {
1186
+ #define MARKER_CONST(name) \
1187
+ rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
171
1188
 
172
- // We don't mark the objects, but we MUST mark the frames, otherwise they
173
- // can be garbage collected.
174
- // This may lead to method entries being unnecessarily retained.
175
- for (VALUE frame: collector->unique_frames) {
176
- rb_gc_mark(frame);
177
- }
1189
+ MARKER_CONST(GVL_THREAD_STARTED);
1190
+ MARKER_CONST(GVL_THREAD_READY);
1191
+ MARKER_CONST(GVL_THREAD_RESUMED);
1192
+ MARKER_CONST(GVL_THREAD_SUSPENDED);
1193
+ MARKER_CONST(GVL_THREAD_EXITED);
1194
+
1195
+ MARKER_CONST(GC_START);
1196
+ MARKER_CONST(GC_END_MARK);
1197
+ MARKER_CONST(GC_END_SWEEP);
1198
+ MARKER_CONST(GC_ENTER);
1199
+ MARKER_CONST(GC_EXIT);
1200
+
1201
+ #undef MARKER_CONST
178
1202
  }
179
1203
 
180
1204
  extern "C" void
181
1205
  Init_vernier(void)
182
1206
  {
183
1207
  rb_mVernier = rb_define_module("Vernier");
1208
+ rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
1209
+ VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
1210
+ rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
1211
+
1212
+ rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
1213
+ rb_undef_alloc_func(rb_cVernierCollector);
1214
+ rb_define_singleton_method(rb_cVernierCollector, "_new", collector_new, 2);
1215
+ rb_define_method(rb_cVernierCollector, "start", collector_start, 0);
1216
+ rb_define_method(rb_cVernierCollector, "sample", collector_sample, 0);
1217
+ rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
1218
+ rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
184
1219
 
185
- rb_define_module_function(rb_mVernier, "trace_retained_start", trace_retained_start, 0);
186
- rb_define_module_function(rb_mVernier, "trace_retained_stop", trace_retained_stop, 0);
1220
+ Init_consts();
187
1221
 
188
- static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, retained_collector_mark, NULL, &_collector);
189
- rb_global_variable(&gc_hook);
1222
+ //static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
1223
+ //rb_global_variable(&gc_hook);
190
1224
  }