vernier 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +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>
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
7
21
 
8
22
  #include "vernier.hh"
9
23
  #include "stack.hh"
24
+
25
+ #include "ruby/ruby.h"
10
26
  #include "ruby/debug.h"
27
+ #include "ruby/thread.h"
11
28
 
12
- 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);
13
41
 
14
- #define numberof(array) ((int)(sizeof(array) / sizeof((array)[0])))
42
+ using namespace std;
15
43
 
16
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
+ }
17
151
 
18
- struct retained_collector {
19
- int allocated_objects = 0;
20
- 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
21
160
 
22
- 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
+ }
23
274
  };
24
275
 
25
276
  struct TraceArg {
@@ -40,149 +291,934 @@ struct TraceArg {
40
291
  }
41
292
  };
42
293
 
43
- static retained_collector _collector;
294
+ struct FrameList {
295
+ std::unordered_map<std::string, int> string_to_idx;
296
+ std::vector<std::string> string_list;
297
+
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);
303
+
304
+ auto result = string_to_idx.insert({str, idx});
305
+ it = result.first;
306
+ }
307
+
308
+ return it->second;
309
+ }
310
+
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
+ };
44
1099
 
45
- static VALUE tp_newobj;
46
- static VALUE tp_freeobj;
47
1100
  static void
48
- newobj_i(VALUE tpval, void *data) {
49
- retained_collector *collector = static_cast<retained_collector *>(data);
50
- TraceArg tp(tpval);
51
- collector->allocated_objects++;
52
-
53
- VALUE frames_buffer[2048];
54
- int lines_buffer[2048];
55
- int n = rb_profile_frames(0, 2048, frames_buffer, lines_buffer);
56
-
57
- collector->object_frames.emplace(
58
- tp.obj,
59
- make_unique<Stack>(frames_buffer, lines_buffer, n)
60
- );
1101
+ collector_mark(void *data) {
1102
+ BaseCollector *collector = static_cast<BaseCollector *>(data);
1103
+ collector->mark();
61
1104
  }
62
1105
 
63
1106
  static void
64
- freeobj_i(VALUE tpval, void *data) {
65
- retained_collector *collector = static_cast<retained_collector *>(data);
66
- TraceArg tp(tpval);
67
- collector->freed_objects++;
68
-
69
- collector->object_frames.erase(tp.obj);
1107
+ collector_free(void *data) {
1108
+ BaseCollector *collector = static_cast<BaseCollector *>(data);
1109
+ delete collector;
70
1110
  }
71
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
+ };
72
1120
 
73
- static VALUE
74
- trace_retained_start(VALUE self) {
75
- 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
+ }
76
1126
 
77
- tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, collector);
78
- 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);
79
1130
 
80
- rb_tracepoint_enable(tp_newobj);
81
- rb_tracepoint_enable(tp_freeobj);
1131
+ if (!collector->start()) {
1132
+ rb_raise(rb_eRuntimeError, "already running");
1133
+ }
82
1134
 
83
1135
  return Qtrue;
84
1136
  }
85
1137
 
86
- #define sym(name) ID2SYM(rb_intern_const(name))
87
-
88
- // HACK: This isn't public, but the objspace ext uses it
89
- extern "C" size_t rb_obj_memsize_of(VALUE);
1138
+ static VALUE
1139
+ collector_stop(VALUE self) {
1140
+ auto *collector = get_collector(self);
90
1141
 
91
- static const char *
92
- ruby_object_type_name(VALUE obj) {
93
- enum ruby_value_type type = rb_type(obj);
94
-
95
- #define TYPE_CASE(x) case (x): return (#x)
96
-
97
- // Many of these are impossible, but it's easier to just include them
98
- switch (type) {
99
- TYPE_CASE(T_OBJECT);
100
- TYPE_CASE(T_CLASS);
101
- TYPE_CASE(T_MODULE);
102
- TYPE_CASE(T_FLOAT);
103
- TYPE_CASE(T_STRING);
104
- TYPE_CASE(T_REGEXP);
105
- TYPE_CASE(T_ARRAY);
106
- TYPE_CASE(T_HASH);
107
- TYPE_CASE(T_STRUCT);
108
- TYPE_CASE(T_BIGNUM);
109
- TYPE_CASE(T_FILE);
110
- TYPE_CASE(T_DATA);
111
- TYPE_CASE(T_MATCH);
112
- TYPE_CASE(T_COMPLEX);
113
- TYPE_CASE(T_RATIONAL);
114
-
115
- TYPE_CASE(T_NIL);
116
- TYPE_CASE(T_TRUE);
117
- TYPE_CASE(T_FALSE);
118
- TYPE_CASE(T_SYMBOL);
119
- TYPE_CASE(T_FIXNUM);
120
- TYPE_CASE(T_UNDEF);
121
-
122
- TYPE_CASE(T_IMEMO);
123
- TYPE_CASE(T_NODE);
124
- TYPE_CASE(T_ICLASS);
125
- TYPE_CASE(T_ZOMBIE);
126
- TYPE_CASE(T_MOVED);
127
-
128
- default:
129
- return "unknown type";
130
- }
131
- #undef TYPE_CASE
1142
+ VALUE result = collector->stop();
1143
+ return result;
132
1144
  }
133
1145
 
134
1146
  static VALUE
135
- trace_retained_stop(VALUE self) {
136
- rb_tracepoint_disable(tp_newobj);
137
- rb_tracepoint_disable(tp_freeobj);
1147
+ markers(VALUE self) {
1148
+ auto *collector = get_collector(self);
138
1149
 
139
- retained_collector *collector = &_collector;
1150
+ return collector->get_markers();
1151
+ }
140
1152
 
141
- std::stringstream ss;
1153
+ static VALUE
1154
+ collector_sample(VALUE self) {
1155
+ auto *collector = get_collector(self);
142
1156
 
143
- for (auto& it: collector->object_frames) {
144
- VALUE obj = it.first;
145
- const Stack &stack = *it.second;
1157
+ collector->sample();
1158
+ return Qtrue;
1159
+ }
146
1160
 
147
- for (int i = stack.size() - 1; i >= 0; i--) {
148
- const Frame &frame = stack.frame(i);
149
- ss << frame;
150
- 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));
151
1174
  }
152
- ss << ";" << ruby_object_type_name(obj);
153
- ss << " " << rb_obj_memsize_of(obj) << endl;
1175
+ collector = new TimeCollector(interval);
1176
+ } else {
1177
+ rb_raise(rb_eArgError, "invalid mode");
154
1178
  }
155
-
156
- std::string s = ss.str();
157
- VALUE str = rb_str_new(s.c_str(), s.size());
158
-
159
- 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;
160
1182
  }
161
1183
 
162
1184
  static void
163
- retained_collector_mark(void *data) {
164
- 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))
165
1188
 
166
- // We don't mark the objects, but we MUST mark the frames, otherwise they
167
- // can be garbage collected.
168
- // This may lead to method entries being unnecessarily retained.
169
- for (auto& it: collector->object_frames) {
170
- const Stack &stack = *it.second;
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);
171
1194
 
172
- for (int i = 0; i < stack.size(); i++) {
173
- rb_gc_mark(stack.frames[i]);
174
- }
175
- }
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
176
1202
  }
177
1203
 
178
1204
  extern "C" void
179
1205
  Init_vernier(void)
180
1206
  {
181
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);
182
1219
 
183
- rb_define_module_function(rb_mVernier, "trace_retained_start", trace_retained_start, 0);
184
- rb_define_module_function(rb_mVernier, "trace_retained_stop", trace_retained_stop, 0);
1220
+ Init_consts();
185
1221
 
186
- static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, retained_collector_mark, NULL, &_collector);
187
- 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);
188
1224
  }