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.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +19 -10
- data/exe/vernier +3 -0
- data/ext/vernier/extconf.rb +4 -3
- data/ext/vernier/heap_tracker.cc +10 -3
- data/ext/vernier/memory.cc +19 -5
- data/ext/vernier/periodic_thread.hh +17 -2
- data/ext/vernier/signal_safe_semaphore.hh +3 -1
- data/ext/vernier/stack_table.cc +18 -6
- data/ext/vernier/stack_table.hh +24 -26
- data/ext/vernier/vernier.cc +127 -117
- data/ext/vernier/vernier.hh +12 -0
- data/lib/vernier/autorun.rb +2 -1
- data/lib/vernier/collector.rb +5 -0
- data/lib/vernier/hooks/active_support.rb +714 -32
- data/lib/vernier/output/cpuprofile.rb +6 -0
- data/lib/vernier/output/file_listing.rb +56 -27
- data/lib/vernier/output/firefox.rb +21 -19
- data/lib/vernier/output/top.rb +8 -4
- data/lib/vernier/output_helpers.rb +33 -0
- data/lib/vernier/thread_names.rb +6 -0
- data/lib/vernier/version.rb +1 -1
- data/vernier.gemspec +2 -2
- metadata +5 -4
data/ext/vernier/vernier.cc
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
516
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
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
|
-
|
|
638
|
-
|
|
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
|
-
|
|
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
|
|
662
|
-
//cerr << gettid() << " suspended! Stack size:" << thread
|
|
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
|
|
676
|
+
thread->set_state(new_state);
|
|
666
677
|
|
|
667
|
-
if (thread
|
|
668
|
-
thread
|
|
669
|
-
thread
|
|
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
|
|
672
|
-
thread
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
1113
|
-
rb_hash_aset(hash, sym("is_start"), thread->is_start
|
|
1114
|
-
|
|
1115
|
-
|
|
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");
|
data/ext/vernier/vernier.hh
CHANGED
|
@@ -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);
|
data/lib/vernier/autorun.rb
CHANGED
|
@@ -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
|
|
data/lib/vernier/collector.rb
CHANGED
|
@@ -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
|