vernier 1.10.1 → 1.12.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.
@@ -8,7 +8,6 @@
8
8
  #include <sstream>
9
9
  #include <unordered_map>
10
10
  #include <unordered_set>
11
- #include <cassert>
12
11
  #include <atomic>
13
12
  #include <mutex>
14
13
 
@@ -26,9 +25,6 @@
26
25
  #include "ruby/debug.h"
27
26
  #include "ruby/thread.h"
28
27
 
29
- #undef assert
30
- #define assert RUBY_ASSERT_ALWAYS
31
-
32
28
  #ifndef PTR2NUM
33
29
  # define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
34
30
  #endif
@@ -76,6 +72,10 @@ static const char *gvl_event_name(rb_event_flag_t event) {
76
72
  // This is used for communication between the profiler thread and the signal
77
73
  // handlers in the observed thread.
78
74
  struct LiveSample {
75
+ LiveSample() = default;
76
+ LiveSample(const LiveSample &) = delete;
77
+ LiveSample &operator=(const LiveSample &) = delete;
78
+
79
79
  RawSample sample;
80
80
 
81
81
  SignalSafeSemaphore sem_complete;
@@ -244,6 +244,10 @@ class Marker {
244
244
 
245
245
  class MarkerTable {
246
246
  public:
247
+ MarkerTable() = default;
248
+ MarkerTable(const MarkerTable &) = delete;
249
+ MarkerTable &operator=(const MarkerTable &) = delete;
250
+
247
251
  std::mutex mutex;
248
252
  std::vector<Marker> list;
249
253
 
@@ -289,7 +293,11 @@ class GCMarkerTable: public MarkerTable {
289
293
  .gc_state = gc_state
290
294
  }
291
295
  };
292
- list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), -1, info });
296
+ TimeStamp now = TimeStamp::Now();
297
+
298
+ // Take the lock only after calling into Ruby above
299
+ const std::lock_guard<std::mutex> lock(mutex);
300
+ list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, now, -1, info });
293
301
  }
294
302
 
295
303
  void record_gc_end_mark() {
@@ -415,6 +423,23 @@ class SampleList {
415
423
  }
416
424
  };
417
425
 
426
+ static rb_internal_thread_specific_key_t thread_serial_key;
427
+
428
+ // A number identifying th for as long as it lives, kept in its thread
429
+ // specific storage
430
+ static uintptr_t thread_serial(VALUE th) {
431
+ static std::mutex mutex;
432
+ static uintptr_t last_serial = 0;
433
+
434
+ const std::lock_guard<std::mutex> lock(mutex);
435
+ uintptr_t serial = (uintptr_t)rb_internal_thread_specific_get(th, thread_serial_key);
436
+ if (!serial) {
437
+ serial = ++last_serial;
438
+ rb_internal_thread_specific_set(th, thread_serial_key, (void *)serial);
439
+ }
440
+ return serial;
441
+ }
442
+
418
443
  class Thread {
419
444
  public:
420
445
  SampleList samples;
@@ -429,8 +454,10 @@ class Thread {
429
454
  INITIAL,
430
455
  };
431
456
 
432
- VALUE ruby_thread;
433
- VALUE ruby_thread_id;
457
+ uintptr_t serial;
458
+ VALUE ruby_thread_id = Qnil;
459
+ bool is_main = false;
460
+ bool is_start = false;
434
461
  pthread_t pthread_id;
435
462
  native_thread_id_t native_tid;
436
463
  State state;
@@ -445,15 +472,10 @@ class Thread {
445
472
  unique_ptr<MarkerTable> markers;
446
473
 
447
474
  // FIXME: don't use pthread at start
448
- 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) {
449
- //ruby_thread_id = ULL2NUM(ruby_thread);
475
+ Thread(pthread_t pthread_id, uintptr_t serial) : serial(serial), pthread_id(pthread_id), state(State::INITIAL), stack_on_suspend_idx(-1) {
450
476
  native_tid = get_native_thread_id();
451
477
  started_at = state_changed_at = TimeStamp::Now();
452
478
  markers = std::make_unique<MarkerTable>();
453
-
454
- if (state == State::STARTED) {
455
- markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
456
- }
457
479
  }
458
480
 
459
481
  void record_newobj(VALUE obj, StackTable &frame_list) {
@@ -468,12 +490,13 @@ class Thread {
468
490
  }
469
491
  }
470
492
 
471
- void record_fiber(VALUE fiber, StackTable &frame_list) {
493
+ // fiber_id must be computed by the caller: rb_obj_id can raise, and we
494
+ // are called with the thread table mutex held.
495
+ void record_fiber(VALUE fiber_id, StackTable &frame_list) {
472
496
  RawSample sample;
473
497
  sample.sample();
474
498
 
475
499
  int stack_idx = translator.translate(frame_list, sample);
476
- VALUE fiber_id = rb_obj_id(fiber);
477
500
  markers->record(Marker::Type::MARKER_FIBER_SWITCH, stack_idx, { .fiber_data = { .fiber_id = fiber_id } });
478
501
  }
479
502
 
@@ -482,7 +505,7 @@ class Thread {
482
505
  return;
483
506
  }
484
507
  if (new_state == Thread::State::SUSPENDED && state == new_state) {
485
- // on Ruby 3.2 (only?) we may see duplicate suspended states
508
+ // Preserve the original timestamp when Ruby emits duplicate suspended events.
486
509
  return;
487
510
  }
488
511
 
@@ -501,7 +524,7 @@ class Thread {
501
524
  return; // no mutation of current state
502
525
  break;
503
526
  case State::RUNNING:
504
- assert(state == INITIAL || state == State::READY || state == State::RUNNING);
527
+ VERNIER_ASSERT(state == INITIAL || state == State::READY || state == State::RUNNING);
505
528
  pthread_id = pthread_self();
506
529
  native_tid = get_native_thread_id();
507
530
 
@@ -512,29 +535,20 @@ class Thread {
512
535
  }
513
536
  break;
514
537
  case State::READY:
515
- // The ready state means "I would like to do some work, but I can't
516
- // do it right now either because I blocked on IO and now I want the GVL back,
517
- // or because the VM timer put me to sleep"
518
- //
519
- // Threads can be preempted, which means they will have been in "Running"
520
- // state, and then the VM was like "no I need to stop you from working,
521
- // so I'll put you in the 'ready' (or stalled) state"
522
- assert(state == State::INITIAL || state == State::STARTED || state == State::SUSPENDED || state == State::RUNNING);
538
+ // A ready thread is waiting to acquire the GVL.
539
+ VERNIER_ASSERT(state == State::INITIAL || state == State::STARTED || state == State::SUSPENDED);
523
540
  if (state == State::SUSPENDED) {
524
541
  markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
525
542
  }
526
- else if (state == State::RUNNING) {
527
- markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
528
- }
529
543
  break;
530
544
  case State::SUSPENDED:
531
545
  // We can go from RUNNING or STARTED to SUSPENDED
532
- assert(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
546
+ VERNIER_ASSERT(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
533
547
  markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
534
548
  break;
535
549
  case State::STOPPED:
536
550
  // We can go from RUNNING or STARTED or SUSPENDED to STOPPED
537
- assert(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
551
+ VERNIER_ASSERT(state == State::INITIAL || state == State::RUNNING || state == State::STARTED || state == State::SUSPENDED);
538
552
  markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
539
553
  markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
540
554
 
@@ -547,14 +561,6 @@ class Thread {
547
561
  state_changed_at = now;
548
562
  }
549
563
 
550
- bool is_main() {
551
- return rb_thread_main() == ruby_thread;
552
- }
553
-
554
- bool is_start(VALUE start_thread) {
555
- return start_thread == ruby_thread;
556
- }
557
-
558
564
  bool running() {
559
565
  return state != State::STOPPED;
560
566
  }
@@ -573,14 +579,24 @@ class ThreadTable {
573
579
  ThreadTable(StackTable &frame_list) : frame_list(frame_list) {
574
580
  }
575
581
 
582
+ ThreadTable(const ThreadTable &) = delete;
583
+ ThreadTable &operator=(const ThreadTable &) = delete;
584
+
576
585
  void mark() {
577
586
  for (const auto &thread : list) {
578
587
  thread->mark();
579
588
  }
580
589
  }
581
590
 
582
- void initial(VALUE th) {
583
- set_state(Thread::State::INITIAL, th);
591
+ // Must hold the GVL, and th must be in this Ractor
592
+ Thread* add(VALUE th) {
593
+ // Compute ID outside the lock
594
+ VALUE ruby_thread_id = rb_obj_id(th);
595
+
596
+ const std::lock_guard<std::mutex> lock(mutex);
597
+ Thread* thread = find_or_create_thread(th);
598
+ thread->ruby_thread_id = ruby_thread_id;
599
+ return thread;
584
600
  }
585
601
 
586
602
  void started(VALUE th) {
@@ -604,41 +620,34 @@ class ThreadTable {
604
620
  set_state(Thread::State::STOPPED, th);
605
621
  }
606
622
 
607
- private:
608
623
  Thread* find_thread(VALUE th) {
609
624
  // Assumes lock is already held by caller
610
- for (auto &threadptr : list) {
611
- if (thread_equal(th, threadptr->ruby_thread)) {
612
- return threadptr.get();
625
+ uintptr_t serial = (uintptr_t)rb_internal_thread_specific_get(th, thread_serial_key);
626
+ if (serial) {
627
+ for (auto &threadptr : list) {
628
+ if (threadptr->serial == serial) {
629
+ return threadptr.get();
630
+ }
613
631
  }
614
632
  }
615
633
  return nullptr;
616
634
  }
617
635
 
618
- Thread& find_or_create_thread(VALUE th, Thread::State initial_state) {
619
- {
620
- const std::lock_guard<std::mutex> lock(mutex);
621
- Thread* existing = find_thread(th);
622
- if (existing) {
623
- return *existing;
624
- }
625
- }
626
-
627
- // Thread not found, compute ID outside the lock
628
- VALUE ruby_thread_id = rb_obj_id(th);
629
-
630
- const std::lock_guard<std::mutex> lock(mutex);
631
- // Check again in case another thread created it while we were unlocked
636
+ private:
637
+ Thread* find_or_create_thread(VALUE th) {
638
+ // Assumes lock is already held by caller
632
639
  Thread* existing = find_thread(th);
633
640
  if (existing) {
634
- return *existing;
641
+ return existing;
642
+ }
643
+
644
+ // Not from Ruby's timer thread
645
+ if (!ruby_native_thread_p()) {
646
+ return nullptr;
635
647
  }
636
648
 
637
- //fprintf(stderr, "NEW THREAD: th: %p, state: %i\n", th, initial_state);
638
- auto new_thread = std::make_unique<Thread>(initial_state, pthread_self(), th, ruby_thread_id);
639
- Thread& thread_ref = *new_thread;
640
- list.push_back(std::move(new_thread));
641
- return thread_ref;
649
+ list.push_back(std::make_unique<Thread>(pthread_self(), thread_serial(th)));
650
+ return list.back().get();
642
651
  }
643
652
 
644
653
  void set_state(Thread::State new_state, VALUE th) {
@@ -649,33 +658,31 @@ class ThreadTable {
649
658
 
650
659
  //fprintf(stderr, "th %p (tid: %i) from %s to %s\n", (void *)th, native_tid, gvl_event_name(state), gvl_event_name(new_state));
651
660
 
652
- Thread& thread = find_or_create_thread(th, new_state);
653
-
654
661
  const std::lock_guard<std::mutex> lock(mutex);
655
662
 
656
- if (new_state == Thread::State::SUSPENDED || new_state == Thread::State::READY && (thread.state != Thread::State::SUSPENDED)) {
663
+ Thread* thread = find_or_create_thread(th);
664
+ if (!thread) {
665
+ return;
666
+ }
657
667
 
668
+ if (new_state == Thread::State::SUSPENDED) {
658
669
  RawSample sample;
659
670
  sample.sample();
660
671
 
661
- thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
662
- //cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
672
+ thread->stack_on_suspend_idx = thread->translator.translate(frame_list, sample);
673
+ //cerr << gettid() << " suspended! Stack size:" << thread->stack_on_suspend.size() << endl;
663
674
  }
664
675
 
665
- thread.set_state(new_state);
676
+ thread->set_state(new_state);
666
677
 
667
- if (thread.state == Thread::State::RUNNING) {
668
- thread.pthread_id = pthread_self();
669
- thread.native_tid = get_native_thread_id();
678
+ if (thread->state == Thread::State::RUNNING) {
679
+ thread->pthread_id = pthread_self();
680
+ thread->native_tid = get_native_thread_id();
670
681
  } else {
671
- thread.pthread_id = 0;
672
- thread.native_tid = 0;
682
+ thread->pthread_id = 0;
683
+ thread->native_tid = 0;
673
684
  }
674
685
  }
675
-
676
- bool thread_equal(VALUE a, VALUE b) {
677
- return a == b;
678
- }
679
686
  };
680
687
 
681
688
  class BaseCollector {
@@ -696,6 +703,9 @@ class BaseCollector {
696
703
  }
697
704
  virtual ~BaseCollector() {}
698
705
 
706
+ BaseCollector(const BaseCollector &) = delete;
707
+ BaseCollector &operator=(const BaseCollector &) = delete;
708
+
699
709
  virtual bool start() {
700
710
  if (running) {
701
711
  return false;
@@ -751,6 +761,9 @@ class GlobalSignalHandler {
751
761
  static LiveSample *live_sample;
752
762
 
753
763
  public:
764
+ GlobalSignalHandler(const GlobalSignalHandler &) = delete;
765
+ GlobalSignalHandler &operator=(const GlobalSignalHandler &) = delete;
766
+
754
767
  static GlobalSignalHandler *get_instance() {
755
768
  static GlobalSignalHandler instance;
756
769
  return &instance;
@@ -773,7 +786,7 @@ class GlobalSignalHandler {
773
786
  bool record_sample(LiveSample &sample, pthread_t pthread_id) {
774
787
  const std::lock_guard<std::mutex> lock(mutex);
775
788
 
776
- assert(pthread_id);
789
+ VERNIER_ASSERT_ALWAYS(pthread_id);
777
790
 
778
791
  live_sample = &sample;
779
792
  int rc = pthread_kill(pthread_id, SIGPROF);
@@ -789,11 +802,13 @@ class GlobalSignalHandler {
789
802
  }
790
803
 
791
804
  private:
805
+ GlobalSignalHandler() = default;
806
+
792
807
  std::mutex mutex;
793
- int count;
808
+ int count = 0;
794
809
 
795
810
  static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
796
- assert(live_sample);
811
+ VERNIER_ASSERT_ALWAYS(live_sample);
797
812
  live_sample->sample_current_thread();
798
813
  }
799
814
 
@@ -819,7 +834,7 @@ class TimeCollector : public BaseCollector {
819
834
  class TimeCollectorThread : public PeriodicThread {
820
835
  TimeCollector &time_collector;
821
836
 
822
- void run_iteration() {
837
+ void run_iteration() override {
823
838
  time_collector.run_iteration();
824
839
  }
825
840
 
@@ -863,32 +878,26 @@ class TimeCollector : public BaseCollector {
863
878
  }
864
879
  allocation_tick = 0;
865
880
 
866
- VALUE current_thread = rb_thread_current();
867
881
  threads.mutex.lock();
868
- for (auto &threadptr : threads.list) {
869
- auto &thread = *threadptr;
870
- if (current_thread == thread.ruby_thread) {
871
- thread.record_newobj(obj, threads.frame_list);
872
- break;
873
- }
882
+ Thread *thread = threads.find_thread(rb_thread_current());
883
+ if (thread) {
884
+ thread->record_newobj(obj, threads.frame_list);
874
885
  }
875
886
  threads.mutex.unlock();
876
887
 
877
888
  }
878
889
 
879
890
  void record_fiber(VALUE th, VALUE fiber) {
891
+ VALUE fiber_id = rb_obj_id(fiber);
880
892
  threads.mutex.lock();
881
- for (auto &threadptr : threads.list) {
882
- auto &thread = *threadptr;
883
- if (th == thread.ruby_thread) {
884
- thread.record_fiber(fiber, threads.frame_list);
885
- break;
886
- }
893
+ Thread *thread = threads.find_thread(th);
894
+ if (thread) {
895
+ thread->record_fiber(fiber_id, threads.frame_list);
887
896
  }
888
897
  threads.mutex.unlock();
889
898
  }
890
899
 
891
- void write_meta(VALUE meta, VALUE result) {
900
+ void write_meta(VALUE meta, VALUE result) override {
892
901
  BaseCollector::write_meta(meta, result);
893
902
  rb_hash_aset(meta, sym("interval"), ULL2NUM(interval.microseconds()));
894
903
  rb_hash_aset(meta, sym("allocation_interval"), ULL2NUM(allocation_interval));
@@ -958,6 +967,7 @@ class TimeCollector : public BaseCollector {
958
967
  collector->record_fiber(rb_thread_current(), rb_fiber_current());
959
968
  break;
960
969
  case RUBY_EVENT_THREAD_BEGIN:
970
+ collector->threads.add(self);
961
971
  collector->threads.started(self);
962
972
  break;
963
973
  case RUBY_EVENT_THREAD_END:
@@ -990,17 +1000,7 @@ class TimeCollector : public BaseCollector {
990
1000
 
991
1001
  static void internal_thread_event_cb(rb_event_flag_t event, const rb_internal_thread_event_data_t *event_data, void *data) {
992
1002
  TimeCollector *collector = static_cast<TimeCollector *>(data);
993
- VALUE thread = Qnil;
994
-
995
- #if HAVE_RB_INTERNAL_THREAD_EVENT_DATA_T_THREAD
996
- thread = event_data->thread;
997
- #else
998
- // We may arrive here when starting a thread with
999
- // RUBY_INTERNAL_THREAD_EVENT_READY before the thread is actually set up.
1000
- if (!ruby_native_thread_p()) return;
1001
-
1002
- thread = rb_thread_current();
1003
- #endif
1003
+ VALUE thread = event_data->thread;
1004
1004
 
1005
1005
  auto native_tid = get_native_thread_id();
1006
1006
  //cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
@@ -1029,7 +1029,7 @@ class TimeCollector : public BaseCollector {
1029
1029
 
1030
1030
  rb_internal_thread_event_hook_t *thread_hook;
1031
1031
 
1032
- bool start() {
1032
+ bool start() override {
1033
1033
  if (!BaseCollector::start()) {
1034
1034
  return false;
1035
1035
  }
@@ -1038,7 +1038,9 @@ class TimeCollector : public BaseCollector {
1038
1038
  VALUE all_threads = rb_funcall(rb_path2class("Thread"), rb_intern("list"), 0);
1039
1039
  for (int i = 0; i < RARRAY_LEN(all_threads); i++) {
1040
1040
  VALUE thread = RARRAY_AREF(all_threads, i);
1041
- this->threads.initial(thread);
1041
+ Thread *record = this->threads.add(thread);
1042
+ record->is_main = thread == rb_thread_main();
1043
+ record->is_start = thread == start_thread;
1042
1044
  }
1043
1045
 
1044
1046
  if (allocation_interval > 0) {
@@ -1066,7 +1068,7 @@ class TimeCollector : public BaseCollector {
1066
1068
  return true;
1067
1069
  }
1068
1070
 
1069
- VALUE stop() {
1071
+ VALUE stop() override {
1070
1072
  BaseCollector::stop();
1071
1073
 
1072
1074
  collector_thread.stop();
@@ -1091,7 +1093,7 @@ class TimeCollector : public BaseCollector {
1091
1093
  return result;
1092
1094
  }
1093
1095
 
1094
- VALUE build_collector_result() {
1096
+ VALUE build_collector_result() override {
1095
1097
  VALUE result = BaseCollector::build_collector_result();
1096
1098
 
1097
1099
  VALUE threads = rb_hash_new();
@@ -1109,16 +1111,22 @@ class TimeCollector : public BaseCollector {
1109
1111
  if (!thread->stopped_at.zero()) {
1110
1112
  rb_hash_aset(hash, sym("stopped_at"), ULL2NUM(thread->stopped_at.nanoseconds()));
1111
1113
  }
1112
- rb_hash_aset(hash, sym("is_main"), thread->is_main() ? Qtrue : Qfalse);
1113
- rb_hash_aset(hash, sym("is_start"), thread->is_start(BaseCollector::start_thread) ? Qtrue : Qfalse);
1114
-
1115
- rb_hash_aset(threads, thread->ruby_thread_id, hash);
1114
+ rb_hash_aset(hash, sym("is_main"), thread->is_main ? Qtrue : Qfalse);
1115
+ rb_hash_aset(hash, sym("is_start"), thread->is_start ? Qtrue : Qfalse);
1116
+
1117
+ VALUE ruby_thread_id = thread->ruby_thread_id;
1118
+ if (NIL_P(ruby_thread_id)) {
1119
+ // We only take object_ids of threads in this Ractor
1120
+ ruby_thread_id = LONG2NUM(-(long)thread->serial);
1121
+ rb_hash_aset(hash, sym("name"), rb_sprintf("thread %lu", (unsigned long)thread->serial));
1122
+ }
1123
+ rb_hash_aset(threads, ruby_thread_id, hash);
1116
1124
  }
1117
1125
 
1118
1126
  return result;
1119
1127
  }
1120
1128
 
1121
- void mark() {
1129
+ void mark() override {
1122
1130
  stack_table->mark_frames();
1123
1131
  rb_gc_mark(stack_table_value);
1124
1132
  threads.mark();
@@ -1242,6 +1250,8 @@ Init_vernier(void)
1242
1250
  sym_fiber_id = sym("fiber_id");
1243
1251
  rb_gc_latest_gc_info(sym_state); // HACK: needs to be warmed so that it can be called during GC
1244
1252
 
1253
+ thread_serial_key = rb_internal_thread_specific_key_create();
1254
+
1245
1255
  rb_mVernier = rb_define_module("Vernier");
1246
1256
  rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
1247
1257
  VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
@@ -2,6 +2,18 @@
2
2
  #define VERNIER_H 1
3
3
 
4
4
  #include "ruby.h"
5
+ #include "ruby/assert.h"
6
+
7
+ // VERNIER_ASSERT is RUBY_ASSERT, which is only active when Ruby itself was
8
+ // built with RUBY_DEBUG. Building with VERNIER_ALWAYS_ASSERT (see extconf.rb)
9
+ // makes it RUBY_ASSERT_ALWAYS. Use VERNIER_ASSERT_ALWAYS for checks that must
10
+ // run in every build.
11
+ #ifdef VERNIER_ALWAYS_ASSERT
12
+ # define VERNIER_ASSERT RUBY_ASSERT_ALWAYS
13
+ #else
14
+ # define VERNIER_ASSERT RUBY_ASSERT
15
+ #endif
16
+ #define VERNIER_ASSERT_ALWAYS RUBY_ASSERT_ALWAYS
5
17
 
6
18
  // HACK: This isn't public, but the objspace ext uses it
7
19
  extern "C" size_t rb_obj_memsize_of(VALUE);
@@ -20,6 +20,7 @@ module Vernier
20
20
  interval = options.fetch(:interval, 500).to_i
21
21
  allocation_interval = options.fetch(:allocation_interval, 0).to_i
22
22
  hooks = options.fetch(:hooks, "").split(",")
23
+ hide_anonymous_threads = options[:hide_anonymous_threads] || false
23
24
  metadata = if options[:metadata]
24
25
  JSON.parse(@options[:metadata].unpack1("m")).to_h { |k, v| [k.to_sym, v] }
25
26
  else
@@ -28,7 +29,7 @@ module Vernier
28
29
 
29
30
  STDERR.puts("starting profiler with interval #{interval} and allocation interval #{allocation_interval}")
30
31
 
31
- @collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:, metadata:)
32
+ @collector = Vernier::Collector.new(:wall, interval:, allocation_interval:, hooks:, metadata:, hide_anonymous_threads:)
32
33
  @collector.start
33
34
  end
34
35
 
@@ -29,6 +29,7 @@ module Vernier
29
29
  result.instance_variable_set(:@threads, {
30
30
  0 => {
31
31
  tid: 0,
32
+ is_main: true,
32
33
  name: "custom",
33
34
  started_at: @started_at,
34
35
  samples: @samples,
@@ -80,6 +81,7 @@ module Vernier
80
81
  result.instance_variable_set(:@threads, {
81
82
  0 => {
82
83
  tid: 0,
84
+ is_main: true,
83
85
  name: "retained memory",
84
86
  started_at: @started_at,
85
87
  samples: samples,
@@ -116,6 +118,7 @@ module Vernier
116
118
  @mode = mode
117
119
  @out = options[:out]
118
120
  @format = options[:format]
121
+ @hide_anonymous_threads = options[:hide_anonymous_threads]
119
122
 
120
123
  @markers = []
121
124
  @hooks = []
@@ -193,6 +196,7 @@ module Vernier
193
196
  result.meta[:out] = @out
194
197
  result.meta[:gc] = @gc
195
198
  result.meta[:user_metadata] = @user_metadata
199
+ result.meta[:hide_anonymous_threads] = @hide_anonymous_threads
196
200
 
197
201
  result.stack_table = stack_table
198
202
  @thread_names.finish
@@ -203,6 +207,7 @@ module Vernier
203
207
 
204
208
  result.threads.each do |obj_id, thread|
205
209
  thread[:name] ||= @thread_names[obj_id]
210
+ thread[:anonymous] ||= @thread_names.anonymous?(obj_id)
206
211
  end
207
212
 
208
213
  result.hooks = @hooks