vernier 1.8.1 → 1.9.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.
@@ -19,6 +19,7 @@
19
19
  #include "timestamp.hh"
20
20
  #include "periodic_thread.hh"
21
21
  #include "signal_safe_semaphore.hh"
22
+ #include "stack_table.hh"
22
23
 
23
24
  #include "ruby/ruby.h"
24
25
  #include "ruby/encoding.h"
@@ -43,18 +44,13 @@
43
44
  RUBY_EVENT_FIBER_SWITCH | \
44
45
  RUBY_EVENT_THREAD_END
45
46
 
46
- #define sym(name) ID2SYM(rb_intern_const(name))
47
-
48
- // HACK: This isn't public, but the objspace ext uses it
49
- extern "C" size_t rb_obj_memsize_of(VALUE);
50
-
51
47
  using namespace std;
52
48
 
53
49
  VALUE rb_mVernier;
54
50
  static VALUE rb_cVernierResult;
55
51
  static VALUE rb_mVernierMarkerType;
56
52
  static VALUE rb_cVernierCollector;
57
- static VALUE rb_cStackTable;
53
+ static VALUE rb_cTimeCollector;
58
54
 
59
55
  static VALUE sym_state, sym_gc_by, sym_fiber_id;
60
56
 
@@ -74,126 +70,6 @@ static const char *gvl_event_name(rb_event_flag_t event) {
74
70
  return "no-event";
75
71
  }
76
72
 
77
- struct FuncInfo {
78
- static const char *label_cstr(VALUE frame) {
79
- VALUE label = rb_profile_frame_full_label(frame);
80
- // Currently (2025-03-22, Ruby 3.4.2) this occurs when an iseq method
81
- // entry is replaced with a refinement
82
- if (NIL_P(label)) return "(nil)";
83
- return StringValueCStr(label);
84
- }
85
-
86
- static const char *file_cstr(VALUE frame) {
87
- VALUE file = rb_profile_frame_absolute_path(frame);
88
- if (NIL_P(file))
89
- file = rb_profile_frame_path(frame);
90
- if (NIL_P(file)) {
91
- return "(nil)";
92
- } else {
93
- return StringValueCStr(file);
94
- }
95
- }
96
-
97
- static int first_lineno_int(VALUE frame) {
98
- VALUE first_lineno = rb_profile_frame_first_lineno(frame);
99
- return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
100
- }
101
-
102
- FuncInfo(VALUE frame) :
103
- label(label_cstr(frame)),
104
- file(file_cstr(frame)),
105
- first_lineno(first_lineno_int(frame)) { }
106
-
107
- std::string label;
108
- std::string file;
109
- int first_lineno;
110
- };
111
-
112
- bool operator==(const FuncInfo& lhs, const FuncInfo& rhs) noexcept {
113
- return
114
- lhs.label == rhs.label &&
115
- lhs.file == rhs.file &&
116
- lhs.first_lineno == rhs.first_lineno;
117
- }
118
-
119
- struct Frame {
120
- VALUE frame;
121
- int line;
122
- };
123
-
124
- bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
125
- return lhs.frame == rhs.frame && lhs.line == rhs.line;
126
- }
127
-
128
- bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
129
- return !(lhs == rhs);
130
- }
131
-
132
- namespace std {
133
- template<>
134
- struct hash<Frame>
135
- {
136
- std::size_t operator()(Frame const& s) const noexcept
137
- {
138
- return s.frame ^ s.line;
139
- }
140
- };
141
- }
142
-
143
- class RawSample {
144
- public:
145
-
146
- constexpr static int MAX_LEN = 2048;
147
-
148
- private:
149
-
150
- VALUE frames[MAX_LEN];
151
- int lines[MAX_LEN];
152
- int len;
153
- int offset;
154
- bool gc;
155
-
156
- public:
157
-
158
- RawSample() : len(0), gc(false), offset(0) { }
159
-
160
- int size() const {
161
- return len - offset;
162
- }
163
-
164
- Frame frame(int i) const {
165
- int idx = len - i - 1;
166
- if (idx < 0) throw std::out_of_range("VERNIER BUG: index out of range");
167
- const Frame frame = {frames[idx], lines[idx]};
168
- return frame;
169
- }
170
-
171
- void sample(int offset = 0) {
172
- clear();
173
-
174
- if (!ruby_native_thread_p()) {
175
- return;
176
- }
177
-
178
- if (rb_during_gc()) {
179
- gc = true;
180
- } else {
181
- len = rb_profile_frames(0, MAX_LEN, frames, lines);
182
- this->offset = std::min(offset, len);
183
- }
184
- }
185
-
186
- void clear() {
187
- len = 0;
188
- offset = 0;
189
- gc = false;
190
- }
191
-
192
- bool empty() const {
193
- return len <= offset;
194
- }
195
- };
196
-
197
73
  // Based very loosely on the design of Gecko's SigHandlerCoordinator
198
74
  // This is used for communication between the profiler thread and the signal
199
75
  // handlers in the observed thread.
@@ -227,395 +103,6 @@ struct LiveSample {
227
103
  }
228
104
  };
229
105
 
230
- template <typename K>
231
- class IndexMap {
232
- public:
233
- std::unordered_map<K, int> to_idx;
234
- std::vector<K> list;
235
-
236
- const K& operator[](int i) const noexcept {
237
- return list[i];
238
- }
239
-
240
- size_t size() const noexcept {
241
- return list.size();
242
- }
243
-
244
- int index(const K key) {
245
- auto it = to_idx.find(key);
246
- if (it == to_idx.end()) {
247
- int idx = list.size();
248
- list.push_back(key);
249
-
250
- auto result = to_idx.insert({key, idx});
251
- it = result.first;
252
- }
253
-
254
- return it->second;
255
- }
256
-
257
- void clear() {
258
- list.clear();
259
- to_idx.clear();
260
- }
261
- };
262
-
263
- struct StackTable {
264
- private:
265
-
266
- struct FrameWithInfo {
267
- Frame frame;
268
- FuncInfo info;
269
- };
270
-
271
- IndexMap<Frame> frame_map;
272
-
273
- IndexMap<VALUE> func_map;
274
- std::vector<FuncInfo> func_info_list;
275
-
276
- struct StackNode {
277
- std::unordered_map<Frame, int> children;
278
- Frame frame;
279
- int parent;
280
- int index;
281
-
282
- StackNode(Frame frame, int index, int parent) : frame(frame), index(index), parent(parent) {}
283
-
284
- // root
285
- StackNode() : frame(Frame{0, 0}), index(-1), parent(-1) {}
286
- };
287
-
288
- // This mutex guards the StackNodes only. The rest of the maps and vectors
289
- // should be guarded by the GVL
290
- std::mutex stack_mutex;
291
-
292
- StackNode root_stack_node;
293
- vector<StackNode> stack_node_list;
294
- int stack_node_list_finalized_idx = 0;
295
-
296
- StackNode *next_stack_node(StackNode *node, Frame frame) {
297
- auto search = node->children.find(frame);
298
- if (search == node->children.end()) {
299
- // insert a new node
300
- int next_node_idx = stack_node_list.size();
301
- node->children[frame] = next_node_idx;
302
- stack_node_list.emplace_back(
303
- frame,
304
- next_node_idx,
305
- node->index
306
- );
307
- return &stack_node_list[next_node_idx];
308
- } else {
309
- int node_idx = search->second;
310
- return &stack_node_list[node_idx];
311
- }
312
- }
313
-
314
- public:
315
-
316
- int stack_index(const RawSample &stack) {
317
- if (stack.empty()) {
318
- throw std::runtime_error("VERNIER BUG: empty stack");
319
- }
320
-
321
- const std::lock_guard<std::mutex> lock(stack_mutex);
322
-
323
- StackNode *node = &root_stack_node;
324
- for (int i = 0; i < stack.size(); i++) {
325
- Frame frame = stack.frame(i);
326
- node = next_stack_node(node, frame);
327
- }
328
- return node->index;
329
- }
330
-
331
- int stack_parent(int stack_idx) {
332
- const std::lock_guard<std::mutex> lock(stack_mutex);
333
- if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
334
- return -1;
335
- } else {
336
- return stack_node_list[stack_idx].parent;
337
- }
338
- }
339
-
340
- int stack_frame(int stack_idx) {
341
- const std::lock_guard<std::mutex> lock(stack_mutex);
342
- if (stack_idx < 0 || stack_idx >= stack_node_list.size()) {
343
- return -1;
344
- } else {
345
- return frame_map.index(stack_node_list[stack_idx].frame);
346
- }
347
- }
348
-
349
- // Converts Frames from stacks other tables. "Symbolicates" the frames
350
- // which allocates.
351
- void finalize() {
352
- {
353
- const std::lock_guard<std::mutex> lock(stack_mutex);
354
- for (int i = stack_node_list_finalized_idx; i < stack_node_list.size(); i++) {
355
- const auto &stack_node = stack_node_list[i];
356
- frame_map.index(stack_node.frame);
357
- func_map.index(stack_node.frame.frame);
358
- stack_node_list_finalized_idx = i;
359
- }
360
- }
361
-
362
- for (int i = func_info_list.size(); i < func_map.size(); i++) {
363
- const auto &func = func_map[i];
364
- // must not hold a mutex here
365
- func_info_list.push_back(FuncInfo(func));
366
- }
367
- }
368
-
369
- void mark_frames() {
370
- const std::lock_guard<std::mutex> lock(stack_mutex);
371
-
372
- for (auto stack_node: stack_node_list) {
373
- rb_gc_mark(stack_node.frame.frame);
374
- }
375
- }
376
-
377
- StackNode *convert_stack(StackTable &other, int original_idx) {
378
- if (original_idx < 0) {
379
- return &root_stack_node;
380
- }
381
-
382
- StackNode &original_node = other.stack_node_list[original_idx];
383
- StackNode *parent_node = convert_stack(other, original_node.parent);
384
- StackNode *node = next_stack_node(parent_node, original_node.frame);
385
-
386
- return node;
387
- }
388
-
389
- static VALUE stack_table_convert(VALUE self, VALUE other, VALUE original_stack);
390
-
391
- static VALUE stack_table_stack_count(VALUE self);
392
- static VALUE stack_table_frame_count(VALUE self);
393
- static VALUE stack_table_func_count(VALUE self);
394
-
395
- static VALUE stack_table_frame_line_no(VALUE self, VALUE idxval);
396
- static VALUE stack_table_frame_func_idx(VALUE self, VALUE idxval);
397
- static VALUE stack_table_func_name(VALUE self, VALUE idxval);
398
- static VALUE stack_table_func_filename(VALUE self, VALUE idxval);
399
- static VALUE stack_table_func_first_lineno(VALUE self, VALUE idxval);
400
-
401
- friend class SampleTranslator;
402
- };
403
-
404
- static void
405
- stack_table_mark(void *data) {
406
- StackTable *stack_table = static_cast<StackTable *>(data);
407
- stack_table->mark_frames();
408
- }
409
-
410
- static void
411
- stack_table_free(void *data) {
412
- StackTable *stack_table = static_cast<StackTable *>(data);
413
- delete stack_table;
414
- }
415
-
416
- static const rb_data_type_t rb_stack_table_type = {
417
- .wrap_struct_name = "vernier/stack_table",
418
- .function = {
419
- //.dmemsize = rb_collector_memsize,
420
- .dmark = stack_table_mark,
421
- .dfree = stack_table_free,
422
- },
423
- };
424
-
425
- static VALUE
426
- stack_table_new(VALUE self) {
427
- StackTable *stack_table = new StackTable();
428
- VALUE obj = TypedData_Wrap_Struct(self, &rb_stack_table_type, stack_table);
429
- return obj;
430
- }
431
-
432
- static StackTable *get_stack_table(VALUE obj) {
433
- StackTable *stack_table;
434
- TypedData_Get_Struct(obj, StackTable, &rb_stack_table_type, stack_table);
435
- return stack_table;
436
- }
437
-
438
- static VALUE
439
- stack_table_current_stack(int argc, VALUE *argv, VALUE self) {
440
- int offset;
441
- VALUE offset_v;
442
-
443
- rb_scan_args(argc, argv, "01", &offset_v);
444
- if (argc > 0) {
445
- offset = NUM2INT(offset_v) + 1;
446
- } else {
447
- offset = 1;
448
- }
449
-
450
- StackTable *stack_table = get_stack_table(self);
451
- RawSample stack;
452
- stack.sample(offset);
453
- int stack_index = stack_table->stack_index(stack);
454
- return INT2NUM(stack_index);
455
- }
456
-
457
- static VALUE
458
- stack_table_stack_parent_idx(VALUE self, VALUE idxval) {
459
- StackTable *stack_table = get_stack_table(self);
460
- int idx = NUM2INT(idxval);
461
- int parent_idx = stack_table->stack_parent(idx);
462
- if (parent_idx < 0) {
463
- return Qnil;
464
- } else {
465
- return INT2NUM(parent_idx);
466
- }
467
- }
468
-
469
- static VALUE
470
- stack_table_stack_frame_idx(VALUE self, VALUE idxval) {
471
- StackTable *stack_table = get_stack_table(self);
472
- //stack_table->finalize();
473
- int idx = NUM2INT(idxval);
474
- int frame_idx = stack_table->stack_frame(idx);
475
- return frame_idx < 0 ? Qnil : INT2NUM(frame_idx);
476
- }
477
-
478
- VALUE
479
- StackTable::stack_table_stack_count(VALUE self) {
480
- StackTable *stack_table = get_stack_table(self);
481
- int count;
482
- {
483
- const std::lock_guard<std::mutex> lock(stack_table->stack_mutex);
484
- count = stack_table->stack_node_list.size();
485
- }
486
- return INT2NUM(count);
487
- }
488
-
489
- VALUE
490
- StackTable::stack_table_convert(VALUE self, VALUE original_tableval, VALUE original_idxval) {
491
- StackTable *stack_table = get_stack_table(self);
492
- StackTable *original_table = get_stack_table(original_tableval);
493
- int original_idx = NUM2INT(original_idxval);
494
-
495
- int original_size;
496
- {
497
- const std::lock_guard<std::mutex> lock(original_table->stack_mutex);
498
- original_size = original_table->stack_node_list.size();
499
- }
500
-
501
- if (original_idx >= original_size || original_idx < 0) {
502
- rb_raise(rb_eRangeError, "index out of range");
503
- }
504
-
505
- int result_idx;
506
- {
507
- const std::lock_guard<std::mutex> lock1(stack_table->stack_mutex);
508
- const std::lock_guard<std::mutex> lock2(original_table->stack_mutex);
509
- StackNode *node = stack_table->convert_stack(*original_table, original_idx);
510
- result_idx = node->index;
511
- }
512
- return INT2NUM(result_idx);
513
- }
514
-
515
- VALUE
516
- StackTable::stack_table_frame_count(VALUE self) {
517
- StackTable *stack_table = get_stack_table(self);
518
- stack_table->finalize();
519
- int count = stack_table->frame_map.size();
520
- return INT2NUM(count);
521
- }
522
-
523
- VALUE
524
- StackTable::stack_table_func_count(VALUE self) {
525
- StackTable *stack_table = get_stack_table(self);
526
- stack_table->finalize();
527
- int count = stack_table->func_map.size();
528
- return INT2NUM(count);
529
- }
530
-
531
- VALUE
532
- StackTable::stack_table_frame_line_no(VALUE self, VALUE idxval) {
533
- StackTable *stack_table = get_stack_table(self);
534
- stack_table->finalize();
535
- int idx = NUM2INT(idxval);
536
- if (idx < 0 || idx >= stack_table->frame_map.size()) {
537
- return Qnil;
538
- } else {
539
- const auto &frame = stack_table->frame_map[idx];
540
- return INT2NUM(frame.line);
541
- }
542
- }
543
-
544
- VALUE
545
- StackTable::stack_table_frame_func_idx(VALUE self, VALUE idxval) {
546
- StackTable *stack_table = get_stack_table(self);
547
- stack_table->finalize();
548
- int idx = NUM2INT(idxval);
549
- if (idx < 0 || idx >= stack_table->frame_map.size()) {
550
- return Qnil;
551
- } else {
552
- const auto &frame = stack_table->frame_map[idx];
553
- int func_idx = stack_table->func_map.index(frame.frame);
554
- return INT2NUM(func_idx);
555
- }
556
- }
557
-
558
- VALUE
559
- StackTable::stack_table_func_name(VALUE self, VALUE idxval) {
560
- StackTable *stack_table = get_stack_table(self);
561
- stack_table->finalize();
562
- int idx = NUM2INT(idxval);
563
- auto &table = stack_table->func_info_list;
564
- if (idx < 0 || idx >= table.size()) {
565
- return Qnil;
566
- } else {
567
- const auto &func_info = table[idx];
568
- const std::string &label = func_info.label;
569
-
570
- // Ruby constants are in an arbitrary (ASCII compatible) encoding and
571
- // method names are in an arbitrary (ASCII compatible) encoding. These
572
- // can be mixed in the same program.
573
- //
574
- // However, by this point we've lost the chain of what the correct
575
- // encoding should be. Oops!
576
- //
577
- // Instead we'll just guess at UTF-8 which should satisfy most. It won't
578
- // necessarily be valid but that can be scrubbed on the Ruby side.
579
- //
580
- // In the future we might keep class and method name separate for
581
- // longer, preserve encodings, and defer formatting to the Ruby side.
582
- return rb_enc_interned_str(label.c_str(), label.length(), rb_utf8_encoding());
583
- }
584
- }
585
-
586
- VALUE
587
- StackTable::stack_table_func_filename(VALUE self, VALUE idxval) {
588
- StackTable *stack_table = get_stack_table(self);
589
- stack_table->finalize();
590
- int idx = NUM2INT(idxval);
591
- auto &table = stack_table->func_info_list;
592
- if (idx < 0 || idx >= table.size()) {
593
- return Qnil;
594
- } else {
595
- const auto &func_info = table[idx];
596
- const std::string &filename = func_info.file;
597
-
598
- // Technically filesystems are binary and then Ruby interprets that as
599
- // default_external encoding. But to keep things simple for now we are
600
- // going to assume UTF-8.
601
- return rb_enc_interned_str(filename.c_str(), filename.length(), rb_utf8_encoding());
602
- }
603
- }
604
-
605
- VALUE
606
- StackTable::stack_table_func_first_lineno(VALUE self, VALUE idxval) {
607
- StackTable *stack_table = get_stack_table(self);
608
- stack_table->finalize();
609
- int idx = NUM2INT(idxval);
610
- auto &table = stack_table->func_info_list;
611
- if (idx < 0 || idx >= table.size()) {
612
- return Qnil;
613
- } else {
614
- const auto &func_info = table[idx];
615
- return INT2NUM(func_info.first_lineno);
616
- }
617
- }
618
-
619
106
  class SampleTranslator {
620
107
  public:
621
108
  int last_stack_index;
@@ -956,8 +443,7 @@ class Thread {
956
443
  unique_ptr<MarkerTable> markers;
957
444
 
958
445
  // FIXME: don't use pthread at start
959
- Thread(State state, pthread_t pthread_id, VALUE ruby_thread) : pthread_id(pthread_id), ruby_thread(ruby_thread), state(state), stack_on_suspend_idx(-1) {
960
- ruby_thread_id = rb_obj_id(ruby_thread);
446
+ Thread(State state, pthread_t pthread_id, VALUE ruby_thread, VALUE ruby_thread_id) : pthread_id(pthread_id), ruby_thread(ruby_thread), state(state), stack_on_suspend_idx(-1), ruby_thread_id(ruby_thread_id) {
961
447
  //ruby_thread_id = ULL2NUM(ruby_thread);
962
448
  native_tid = get_native_thread_id();
963
449
  started_at = state_changed_at = TimeStamp::Now();
@@ -989,7 +475,7 @@ class Thread {
989
475
  markers->record(Marker::Type::MARKER_FIBER_SWITCH, stack_idx, { .fiber_data = { .fiber_id = fiber_id } });
990
476
  }
991
477
 
992
- void set_state(State new_state) {
478
+ void set_state(State new_state, bool current = true) {
993
479
  if (state == Thread::State::STOPPED) {
994
480
  return;
995
481
  }
@@ -1117,9 +603,43 @@ class ThreadTable {
1117
603
  }
1118
604
 
1119
605
  private:
1120
- void set_state(Thread::State new_state, VALUE th) {
606
+ Thread* find_thread(VALUE th) {
607
+ // Assumes lock is already held by caller
608
+ for (auto &threadptr : list) {
609
+ if (thread_equal(th, threadptr->ruby_thread)) {
610
+ return threadptr.get();
611
+ }
612
+ }
613
+ return nullptr;
614
+ }
615
+
616
+ Thread& find_or_create_thread(VALUE th, Thread::State initial_state) {
617
+ {
618
+ const std::lock_guard<std::mutex> lock(mutex);
619
+ Thread* existing = find_thread(th);
620
+ if (existing) {
621
+ return *existing;
622
+ }
623
+ }
624
+
625
+ // Thread not found, compute ID outside the lock
626
+ VALUE ruby_thread_id = rb_obj_id(th);
627
+
1121
628
  const std::lock_guard<std::mutex> lock(mutex);
629
+ // Check again in case another thread created it while we were unlocked
630
+ Thread* existing = find_thread(th);
631
+ if (existing) {
632
+ return *existing;
633
+ }
634
+
635
+ //fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, initial_state);
636
+ auto new_thread = std::make_unique<Thread>(initial_state, pthread_self(), th, ruby_thread_id);
637
+ Thread& thread_ref = *new_thread;
638
+ list.push_back(std::move(new_thread));
639
+ return thread_ref;
640
+ }
1122
641
 
642
+ void set_state(Thread::State new_state, VALUE th) {
1123
643
  //cerr << "set state=" << new_state << " thread=" << gettid() << endl;
1124
644
 
1125
645
  pid_t native_tid = get_native_thread_id();
@@ -1127,35 +647,28 @@ class ThreadTable {
1127
647
 
1128
648
  //fprintf(stderr, "th %p (tid: %i) from %s to %s\n", (void *)th, native_tid, gvl_event_name(state), gvl_event_name(new_state));
1129
649
 
1130
- for (auto &threadptr : list) {
1131
- auto &thread = *threadptr;
1132
- if (thread_equal(th, thread.ruby_thread)) {
1133
- if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
650
+ Thread& thread = find_or_create_thread(th, new_state);
1134
651
 
1135
- RawSample sample;
1136
- sample.sample();
652
+ const std::lock_guard<std::mutex> lock(mutex);
1137
653
 
1138
- thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
1139
- //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
1140
- }
654
+ if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
1141
655
 
1142
- thread.set_state(new_state);
656
+ RawSample sample;
657
+ sample.sample();
1143
658
 
1144
- if (thread.state == Thread::State::RUNNING) {
1145
- thread.pthread_id = pthread_self();
1146
- thread.native_tid = get_native_thread_id();
1147
- } else {
1148
- thread.pthread_id = 0;
1149
- thread.native_tid = 0;
1150
- }
659
+ thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
660
+ //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
661
+ }
1151
662
 
663
+ thread.set_state(new_state);
1152
664
 
1153
- return;
1154
- }
665
+ if (thread.state == Thread::State::RUNNING) {
666
+ thread.pthread_id = pthread_self();
667
+ thread.native_tid = get_native_thread_id();
668
+ } else {
669
+ thread.pthread_id = 0;
670
+ thread.native_tid = 0;
1155
671
  }
1156
-
1157
- //fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, new_state);
1158
- list.push_back(std::make_unique<Thread>(new_state, pthread_self(), th));
1159
672
  }
1160
673
 
1161
674
  bool thread_equal(VALUE a, VALUE b) {
@@ -1232,209 +745,6 @@ class BaseCollector {
1232
745
  };
1233
746
  };
1234
747
 
1235
- class CustomCollector : public BaseCollector {
1236
- SampleList samples;
1237
-
1238
- void sample() {
1239
- RawSample sample;
1240
- sample.sample();
1241
- int stack_index = stack_table->stack_index(sample);
1242
-
1243
- samples.record_sample(stack_index, TimeStamp::Now(), CATEGORY_NORMAL);
1244
- }
1245
-
1246
- VALUE stop() {
1247
- BaseCollector::stop();
1248
-
1249
- stack_table->finalize();
1250
-
1251
- VALUE result = build_collector_result();
1252
-
1253
- reset();
1254
-
1255
- return result;
1256
- }
1257
-
1258
- VALUE build_collector_result() {
1259
- VALUE result = BaseCollector::build_collector_result();
1260
-
1261
- VALUE threads = rb_hash_new();
1262
- rb_ivar_set(result, rb_intern("@threads"), threads);
1263
-
1264
- VALUE thread_hash = rb_hash_new();
1265
- samples.write_result(thread_hash);
1266
-
1267
- rb_hash_aset(threads, ULL2NUM(0), thread_hash);
1268
- rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
1269
- rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("custom"));
1270
- rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
1271
-
1272
- return result;
1273
- }
1274
-
1275
- public:
1276
-
1277
- CustomCollector(VALUE stack_table) : BaseCollector(stack_table) { }
1278
- };
1279
-
1280
- class RetainedCollector : public BaseCollector {
1281
- void reset() {
1282
- object_frames.clear();
1283
- object_list.clear();
1284
-
1285
- BaseCollector::reset();
1286
- }
1287
-
1288
- void record(VALUE obj) {
1289
- RawSample sample;
1290
- sample.sample();
1291
- if (sample.empty()) {
1292
- // During thread allocation we allocate one object without a frame
1293
- // (as of Ruby 3.3)
1294
- // Ideally we'd allow empty samples to be represented
1295
- return;
1296
- }
1297
- int stack_index = stack_table->stack_index(sample);
1298
-
1299
- object_list.push_back(obj);
1300
- object_frames.emplace(obj, stack_index);
1301
- }
1302
-
1303
- std::unordered_map<VALUE, int> object_frames;
1304
- std::vector<VALUE> object_list;
1305
-
1306
- VALUE tp_newobj = Qnil;
1307
- VALUE tp_freeobj = Qnil;
1308
-
1309
- static void newobj_i(VALUE tpval, void *data) {
1310
- RetainedCollector *collector = static_cast<RetainedCollector *>(data);
1311
- rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1312
- VALUE obj = rb_tracearg_object(tparg);
1313
-
1314
- collector->record(obj);
1315
- }
1316
-
1317
- static void freeobj_i(VALUE tpval, void *data) {
1318
- RetainedCollector *collector = static_cast<RetainedCollector *>(data);
1319
- rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
1320
- VALUE obj = rb_tracearg_object(tparg);
1321
-
1322
- collector->object_frames.erase(obj);
1323
- }
1324
-
1325
- public:
1326
-
1327
- RetainedCollector(VALUE stack_table) : BaseCollector(stack_table) { }
1328
-
1329
- bool start() {
1330
- if (!BaseCollector::start()) {
1331
- return false;
1332
- }
1333
-
1334
- tp_newobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, this);
1335
- tp_freeobj = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, this);
1336
-
1337
- rb_tracepoint_enable(tp_newobj);
1338
- rb_tracepoint_enable(tp_freeobj);
1339
-
1340
- return true;
1341
- }
1342
-
1343
- VALUE stop() {
1344
- BaseCollector::stop();
1345
-
1346
- // GC before we start turning stacks into strings
1347
- rb_gc();
1348
-
1349
- // Stop tracking any more new objects, but we'll continue tracking free'd
1350
- // objects as we may be able to free some as we remove our own references
1351
- // to stack frames.
1352
- rb_tracepoint_disable(tp_newobj);
1353
- tp_newobj = Qnil;
1354
-
1355
- stack_table->finalize();
1356
-
1357
- // We should have collected info for all our frames, so no need to continue
1358
- // marking them
1359
- // FIXME: previously here we cleared the list of frames so we would stop
1360
- // marking them. Maybe now we should set a flag so that we stop marking them
1361
-
1362
- // GC again
1363
- rb_gc();
1364
-
1365
- rb_tracepoint_disable(tp_freeobj);
1366
- tp_freeobj = Qnil;
1367
-
1368
- VALUE result = build_collector_result();
1369
-
1370
- reset();
1371
-
1372
- return result;
1373
- }
1374
-
1375
- VALUE build_collector_result() {
1376
- RetainedCollector *collector = this;
1377
- StackTable &frame_list = *collector->stack_table;
1378
-
1379
- VALUE result = BaseCollector::build_collector_result();
1380
-
1381
- VALUE threads = rb_hash_new();
1382
- rb_ivar_set(result, rb_intern("@threads"), threads);
1383
- VALUE thread_hash = rb_hash_new();
1384
- rb_hash_aset(threads, ULL2NUM(0), thread_hash);
1385
-
1386
- rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
1387
- VALUE samples = rb_ary_new();
1388
- rb_hash_aset(thread_hash, sym("samples"), samples);
1389
- VALUE weights = rb_ary_new();
1390
- rb_hash_aset(thread_hash, sym("weights"), weights);
1391
-
1392
- rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("retained memory"));
1393
- rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(collector->started_at.nanoseconds()));
1394
-
1395
- for (auto& obj: collector->object_list) {
1396
- const auto search = collector->object_frames.find(obj);
1397
- if (search != collector->object_frames.end()) {
1398
- int stack_index = search->second;
1399
-
1400
- rb_ary_push(samples, INT2NUM(stack_index));
1401
- rb_ary_push(weights, INT2NUM(rb_obj_memsize_of(obj)));
1402
- }
1403
- }
1404
-
1405
- return result;
1406
- }
1407
-
1408
- void mark() {
1409
- // We don't mark the objects, but we MUST mark the frames, otherwise they
1410
- // can be garbage collected.
1411
- // When we stop collection we will stringify the remaining frames, and then
1412
- // clear them from the set, allowing them to be removed from out output.
1413
- stack_table->mark_frames();
1414
- rb_gc_mark(stack_table_value);
1415
-
1416
- rb_gc_mark(tp_newobj);
1417
- rb_gc_mark(tp_freeobj);
1418
- }
1419
-
1420
- void compact() {
1421
- RetainedCollector *collector = this;
1422
- for (auto& obj: collector->object_list) {
1423
- VALUE reloc_obj = rb_gc_location(obj);
1424
-
1425
- const auto search = collector->object_frames.find(obj);
1426
- if (search != collector->object_frames.end()) {
1427
- int stack_index = search->second;
1428
-
1429
- collector->object_frames.erase(search);
1430
- collector->object_frames.emplace(reloc_obj, stack_index);
1431
- }
1432
-
1433
- obj = reloc_obj;
1434
- }
1435
- }
1436
- };
1437
-
1438
748
  class GlobalSignalHandler {
1439
749
  static LiveSample *live_sample;
1440
750
 
@@ -1782,11 +1092,11 @@ class TimeCollector : public BaseCollector {
1782
1092
  VALUE build_collector_result() {
1783
1093
  VALUE result = BaseCollector::build_collector_result();
1784
1094
 
1785
- rb_ivar_set(result, rb_intern("@gc_markers"), this->gc_markers.to_array());
1786
-
1787
1095
  VALUE threads = rb_hash_new();
1788
1096
  rb_ivar_set(result, rb_intern("@threads"), threads);
1789
1097
 
1098
+ rb_ivar_set(result, rb_intern("@gc_markers"), this->gc_markers.to_array());
1099
+
1790
1100
  for (const auto& thread: this->threads.list) {
1791
1101
  VALUE hash = rb_hash_new();
1792
1102
  thread->samples.write_result(hash);
@@ -1840,10 +1150,9 @@ collector_compact(void *data) {
1840
1150
  static const rb_data_type_t rb_collector_type = {
1841
1151
  .wrap_struct_name = "vernier/collector",
1842
1152
  .function = {
1843
- //.dmemsize = rb_collector_memsize,
1844
1153
  .dmark = collector_mark,
1845
1154
  .dfree = collector_free,
1846
- .dsize = NULL,
1155
+ .dsize = nullptr,
1847
1156
  .dcompact = collector_compact,
1848
1157
  },
1849
1158
  };
@@ -1873,30 +1182,11 @@ collector_stop(VALUE self) {
1873
1182
  return result;
1874
1183
  }
1875
1184
 
1876
- static VALUE
1877
- collector_sample(VALUE self) {
1878
- auto *collector = get_collector(self);
1879
-
1880
- collector->sample();
1881
- return Qtrue;
1882
- }
1883
-
1884
- static VALUE
1885
- collector_stack_table(VALUE self) {
1886
- auto *collector = get_collector(self);
1887
-
1888
- return collector->stack_table_value;
1889
- }
1890
-
1891
1185
  static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
1892
1186
  BaseCollector *collector;
1893
1187
 
1894
- VALUE stack_table = stack_table_new(rb_cStackTable);
1895
- if (mode == sym("retained")) {
1896
- collector = new RetainedCollector(stack_table);
1897
- } else if (mode == sym("custom")) {
1898
- collector = new CustomCollector(stack_table);
1899
- } else if (mode == sym("wall")) {
1188
+ VALUE stack_table = StackTable::stack_table_new();
1189
+ if (mode == sym("wall")) {
1900
1190
  VALUE intervalv = rb_hash_aref(options, sym("interval"));
1901
1191
  TimeStamp interval;
1902
1192
  if (NIL_P(intervalv)) {
@@ -1920,6 +1210,7 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
1920
1210
  rb_raise(rb_eArgError, "invalid mode");
1921
1211
  }
1922
1212
  VALUE obj = TypedData_Wrap_Struct(self, &rb_collector_type, collector);
1213
+ rb_ivar_set(obj, rb_intern("@stack_table"), stack_table);
1923
1214
  rb_funcall(obj, rb_intern("initialize"), 2, mode, options);
1924
1215
  return obj;
1925
1216
  }
@@ -1956,31 +1247,16 @@ Init_vernier(void)
1956
1247
  rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
1957
1248
 
1958
1249
  rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
1959
- rb_undef_alloc_func(rb_cVernierCollector);
1960
- rb_define_singleton_method(rb_cVernierCollector, "_new", collector_new, 2);
1961
- rb_define_method(rb_cVernierCollector, "start", collector_start, 0);
1962
- rb_define_method(rb_cVernierCollector, "sample", collector_sample, 0);
1963
- rb_define_method(rb_cVernierCollector, "stack_table", collector_stack_table, 0);
1964
- rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
1965
-
1966
- rb_cStackTable = rb_define_class_under(rb_mVernier, "StackTable", rb_cObject);
1967
- rb_undef_alloc_func(rb_cStackTable);
1968
- rb_define_singleton_method(rb_cStackTable, "new", stack_table_new, 0);
1969
- rb_define_method(rb_cStackTable, "current_stack", stack_table_current_stack, -1);
1970
- rb_define_method(rb_cStackTable, "convert", StackTable::stack_table_convert, 2);
1971
- rb_define_method(rb_cStackTable, "stack_parent_idx", stack_table_stack_parent_idx, 1);
1972
- rb_define_method(rb_cStackTable, "stack_frame_idx", stack_table_stack_frame_idx, 1);
1973
- rb_define_method(rb_cStackTable, "frame_line_no", StackTable::stack_table_frame_line_no, 1);
1974
- rb_define_method(rb_cStackTable, "frame_func_idx", StackTable::stack_table_frame_func_idx, 1);
1975
- rb_define_method(rb_cStackTable, "func_name", StackTable::stack_table_func_name, 1);
1976
- rb_define_method(rb_cStackTable, "func_filename", StackTable::stack_table_func_filename, 1);
1977
- rb_define_method(rb_cStackTable, "func_first_lineno", StackTable::stack_table_func_first_lineno, 1);
1978
- rb_define_method(rb_cStackTable, "stack_count", StackTable::stack_table_stack_count, 0);
1979
- rb_define_method(rb_cStackTable, "frame_count", StackTable::stack_table_frame_count, 0);
1980
- rb_define_method(rb_cStackTable, "func_count", StackTable::stack_table_func_count, 0);
1250
+ rb_cTimeCollector = rb_define_class_under(rb_cVernierCollector, "TimeCollector", rb_cVernierCollector);
1251
+ rb_undef_alloc_func(rb_cTimeCollector);
1252
+ rb_define_singleton_method(rb_cTimeCollector, "new", collector_new, 2);
1253
+ rb_define_method(rb_cTimeCollector, "start", collector_start, 0);
1254
+ rb_define_private_method(rb_cTimeCollector, "finish", collector_stop, 0);
1981
1255
 
1982
1256
  Init_consts(rb_mVernierMarkerPhase);
1983
1257
  Init_memory();
1258
+ Init_stack_table();
1259
+ Init_heap_tracker();
1984
1260
 
1985
1261
  //static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
1986
1262
  //rb_global_variable(&gc_hook);