vernier 0.2.1 → 0.3.1
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/README.md +24 -8
- data/bin/vernier +4 -0
- data/examples/minitest.rb +20 -0
- data/examples/ractor.rb +11 -0
- data/examples/rails.rb +125 -0
- data/exe/vernier +38 -0
- data/ext/vernier/vernier.cc +571 -302
- data/lib/vernier/autorun.rb +65 -0
- data/lib/vernier/collector.rb +47 -28
- data/lib/vernier/marker.rb +11 -11
- data/lib/vernier/output/firefox.rb +76 -45
- data/lib/vernier/result.rb +139 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +6 -133
- data/vernier.gemspec +1 -1
- metadata +12 -5
- data/sig/vernier.rbs +0 -4
data/ext/vernier/vernier.cc
CHANGED
@@ -27,14 +27,20 @@
|
|
27
27
|
#include "ruby/debug.h"
|
28
28
|
#include "ruby/thread.h"
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
# define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x)))
|
31
|
+
|
32
|
+
// Internal TracePoint events we'll monitor during profiling
|
33
|
+
#define RUBY_INTERNAL_EVENTS \
|
32
34
|
RUBY_INTERNAL_EVENT_GC_START | \
|
33
35
|
RUBY_INTERNAL_EVENT_GC_END_MARK | \
|
34
36
|
RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
|
35
37
|
RUBY_INTERNAL_EVENT_GC_ENTER | \
|
36
38
|
RUBY_INTERNAL_EVENT_GC_EXIT
|
37
39
|
|
40
|
+
#define RUBY_NORMAL_EVENTS \
|
41
|
+
RUBY_EVENT_THREAD_BEGIN | \
|
42
|
+
RUBY_EVENT_THREAD_END
|
43
|
+
|
38
44
|
#define sym(name) ID2SYM(rb_intern_const(name))
|
39
45
|
|
40
46
|
// HACK: This isn't public, but the objspace ext uses it
|
@@ -66,13 +72,17 @@ class TimeStamp {
|
|
66
72
|
return TimeStamp(0);
|
67
73
|
}
|
68
74
|
|
69
|
-
|
70
|
-
|
75
|
+
// SleepUntil a specified timestamp
|
76
|
+
// Highly accurate manual sleep time
|
77
|
+
static void SleepUntil(const TimeStamp &target_time) {
|
78
|
+
if (target_time.zero()) return;
|
79
|
+
struct timespec ts = target_time.timespec();
|
71
80
|
|
72
81
|
int res;
|
73
82
|
do {
|
74
|
-
|
75
|
-
|
83
|
+
// do nothing until it's time :)
|
84
|
+
sleep(0);
|
85
|
+
} while (target_time > TimeStamp::Now());
|
76
86
|
}
|
77
87
|
|
78
88
|
static TimeStamp from_microseconds(uint64_t us) {
|
@@ -125,6 +135,14 @@ class TimeStamp {
|
|
125
135
|
return value_ns >= other.value_ns;
|
126
136
|
}
|
127
137
|
|
138
|
+
bool operator==(const TimeStamp &other) const {
|
139
|
+
return value_ns == other.value_ns;
|
140
|
+
}
|
141
|
+
|
142
|
+
bool operator!=(const TimeStamp &other) const {
|
143
|
+
return value_ns != other.value_ns;
|
144
|
+
}
|
145
|
+
|
128
146
|
uint64_t nanoseconds() const {
|
129
147
|
return value_ns;
|
130
148
|
}
|
@@ -202,6 +220,10 @@ bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
|
202
220
|
return lhs.frame == rhs.frame && lhs.line == rhs.line;
|
203
221
|
}
|
204
222
|
|
223
|
+
bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
|
224
|
+
return !(lhs == rhs);
|
225
|
+
}
|
226
|
+
|
205
227
|
namespace std {
|
206
228
|
template<>
|
207
229
|
struct hash<Frame>
|
@@ -275,7 +297,9 @@ struct RawSample {
|
|
275
297
|
}
|
276
298
|
|
277
299
|
Frame frame(int i) const {
|
278
|
-
|
300
|
+
int idx = len - i - 1;
|
301
|
+
if (idx < 0) throw std::out_of_range("out of range");
|
302
|
+
const Frame frame = {frames[idx], lines[idx]};
|
279
303
|
return frame;
|
280
304
|
}
|
281
305
|
|
@@ -337,24 +361,6 @@ struct LiveSample {
|
|
337
361
|
}
|
338
362
|
};
|
339
363
|
|
340
|
-
struct TraceArg {
|
341
|
-
rb_trace_arg_t *tparg;
|
342
|
-
VALUE obj;
|
343
|
-
VALUE path;
|
344
|
-
VALUE line;
|
345
|
-
VALUE mid;
|
346
|
-
VALUE klass;
|
347
|
-
|
348
|
-
TraceArg(VALUE tpval) {
|
349
|
-
tparg = rb_tracearg_from_tracepoint(tpval);
|
350
|
-
obj = rb_tracearg_object(tparg);
|
351
|
-
path = rb_tracearg_path(tparg);
|
352
|
-
line = rb_tracearg_lineno(tparg);
|
353
|
-
mid = rb_tracearg_method_id(tparg);
|
354
|
-
klass = rb_tracearg_defined_class(tparg);
|
355
|
-
}
|
356
|
-
};
|
357
|
-
|
358
364
|
struct FrameList {
|
359
365
|
std::unordered_map<std::string, int> string_to_idx;
|
360
366
|
std::vector<std::string> string_list;
|
@@ -412,27 +418,29 @@ struct FrameList {
|
|
412
418
|
}
|
413
419
|
|
414
420
|
StackNode *node = &root_stack_node;
|
415
|
-
for (int i = stack.size()
|
416
|
-
|
421
|
+
for (int i = 0; i < stack.size(); i++) {
|
422
|
+
Frame frame = stack.frame(i);
|
417
423
|
node = next_stack_node(node, frame);
|
418
424
|
}
|
419
425
|
return node->index;
|
420
426
|
}
|
421
427
|
|
422
|
-
StackNode *next_stack_node(StackNode *node,
|
423
|
-
|
424
|
-
if (
|
428
|
+
StackNode *next_stack_node(StackNode *node, Frame frame) {
|
429
|
+
auto search = node->children.find(frame);
|
430
|
+
if (search == node->children.end()) {
|
425
431
|
// insert a new node
|
426
|
-
next_node_idx = stack_node_list.size();
|
432
|
+
int next_node_idx = stack_node_list.size();
|
427
433
|
node->children[frame] = next_node_idx;
|
428
434
|
stack_node_list.emplace_back(
|
429
435
|
frame,
|
430
436
|
next_node_idx,
|
431
437
|
node->index
|
432
438
|
);
|
439
|
+
return &stack_node_list[next_node_idx];
|
440
|
+
} else {
|
441
|
+
int node_idx = search->second;
|
442
|
+
return &stack_node_list[node_idx];
|
433
443
|
}
|
434
|
-
|
435
|
-
return &stack_node_list[next_node_idx];
|
436
444
|
}
|
437
445
|
|
438
446
|
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
@@ -512,6 +520,372 @@ struct FrameList {
|
|
512
520
|
}
|
513
521
|
};
|
514
522
|
|
523
|
+
class SampleTranslator {
|
524
|
+
public:
|
525
|
+
int last_stack_index;
|
526
|
+
|
527
|
+
Frame frames[RawSample::MAX_LEN];
|
528
|
+
int frame_indexes[RawSample::MAX_LEN];
|
529
|
+
int len;
|
530
|
+
|
531
|
+
SampleTranslator() : len(0), last_stack_index(-1) {
|
532
|
+
}
|
533
|
+
|
534
|
+
int translate(FrameList &frame_list, const RawSample &sample) {
|
535
|
+
int i = 0;
|
536
|
+
for (; i < len && i < sample.size(); i++) {
|
537
|
+
if (frames[i] != sample.frame(i)) {
|
538
|
+
break;
|
539
|
+
}
|
540
|
+
}
|
541
|
+
|
542
|
+
FrameList::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
|
543
|
+
|
544
|
+
for (; i < sample.size(); i++) {
|
545
|
+
Frame frame = sample.frame(i);
|
546
|
+
node = frame_list.next_stack_node(node, frame);
|
547
|
+
|
548
|
+
frames[i] = frame;
|
549
|
+
frame_indexes[i] = node->index;
|
550
|
+
}
|
551
|
+
len = i;
|
552
|
+
|
553
|
+
last_stack_index = node->index;
|
554
|
+
return last_stack_index;
|
555
|
+
}
|
556
|
+
};
|
557
|
+
|
558
|
+
typedef uint64_t native_thread_id_t;
|
559
|
+
static native_thread_id_t get_native_thread_id() {
|
560
|
+
#ifdef __APPLE__
|
561
|
+
uint64_t thread_id;
|
562
|
+
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
563
|
+
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
564
|
+
return thread_id;
|
565
|
+
#else
|
566
|
+
// gettid() is only available as of glibc 2.30
|
567
|
+
pid_t tid = syscall(SYS_gettid);
|
568
|
+
return tid;
|
569
|
+
#endif
|
570
|
+
}
|
571
|
+
|
572
|
+
|
573
|
+
class Marker {
|
574
|
+
public:
|
575
|
+
enum Type {
|
576
|
+
MARKER_GVL_THREAD_STARTED,
|
577
|
+
MARKER_GVL_THREAD_EXITED,
|
578
|
+
|
579
|
+
MARKER_GC_START,
|
580
|
+
MARKER_GC_END_MARK,
|
581
|
+
MARKER_GC_END_SWEEP,
|
582
|
+
MARKER_GC_ENTER,
|
583
|
+
MARKER_GC_EXIT,
|
584
|
+
MARKER_GC_PAUSE,
|
585
|
+
|
586
|
+
MARKER_THREAD_RUNNING,
|
587
|
+
MARKER_THREAD_STALLED,
|
588
|
+
MARKER_THREAD_SUSPENDED,
|
589
|
+
|
590
|
+
MARKER_MAX,
|
591
|
+
};
|
592
|
+
|
593
|
+
// Must match phase types from Gecko
|
594
|
+
enum Phase {
|
595
|
+
INSTANT,
|
596
|
+
INTERVAL,
|
597
|
+
INTERVAL_START,
|
598
|
+
INTERVAL_END
|
599
|
+
};
|
600
|
+
|
601
|
+
Type type;
|
602
|
+
Phase phase;
|
603
|
+
TimeStamp timestamp;
|
604
|
+
TimeStamp finish;
|
605
|
+
native_thread_id_t thread_id;
|
606
|
+
int stack_index = -1;
|
607
|
+
|
608
|
+
VALUE to_array() {
|
609
|
+
VALUE record[6] = {0};
|
610
|
+
record[0] = ULL2NUM(thread_id);
|
611
|
+
record[1] = INT2NUM(type);
|
612
|
+
record[2] = INT2NUM(phase);
|
613
|
+
record[3] = ULL2NUM(timestamp.nanoseconds());
|
614
|
+
|
615
|
+
if (phase == Marker::Phase::INTERVAL) {
|
616
|
+
record[4] = ULL2NUM(finish.nanoseconds());
|
617
|
+
}
|
618
|
+
else {
|
619
|
+
record[4] = Qnil;
|
620
|
+
}
|
621
|
+
record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
|
622
|
+
|
623
|
+
return rb_ary_new_from_values(6, record);
|
624
|
+
}
|
625
|
+
};
|
626
|
+
|
627
|
+
class MarkerTable {
|
628
|
+
TimeStamp last_gc_entry;
|
629
|
+
|
630
|
+
public:
|
631
|
+
std::vector<Marker> list;
|
632
|
+
std::mutex mutex;
|
633
|
+
|
634
|
+
void record_gc_entered() {
|
635
|
+
last_gc_entry = TimeStamp::Now();
|
636
|
+
}
|
637
|
+
|
638
|
+
void record_gc_leave() {
|
639
|
+
list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), get_native_thread_id(), -1 });
|
640
|
+
}
|
641
|
+
|
642
|
+
void record_interval(Marker::Type type, TimeStamp from, TimeStamp to, int stack_index = -1) {
|
643
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
644
|
+
|
645
|
+
list.push_back({ type, Marker::INTERVAL, from, to, get_native_thread_id(), stack_index });
|
646
|
+
}
|
647
|
+
|
648
|
+
void record(Marker::Type type, int stack_index = -1) {
|
649
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
650
|
+
|
651
|
+
list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), get_native_thread_id(), stack_index });
|
652
|
+
}
|
653
|
+
};
|
654
|
+
|
655
|
+
enum Category{
|
656
|
+
CATEGORY_NORMAL,
|
657
|
+
CATEGORY_IDLE
|
658
|
+
};
|
659
|
+
|
660
|
+
class SampleList {
|
661
|
+
public:
|
662
|
+
|
663
|
+
std::vector<int> stacks;
|
664
|
+
std::vector<TimeStamp> timestamps;
|
665
|
+
std::vector<native_thread_id_t> threads;
|
666
|
+
std::vector<Category> categories;
|
667
|
+
std::vector<int> weights;
|
668
|
+
|
669
|
+
size_t size() {
|
670
|
+
return stacks.size();
|
671
|
+
}
|
672
|
+
|
673
|
+
bool empty() {
|
674
|
+
return size() == 0;
|
675
|
+
}
|
676
|
+
|
677
|
+
void record_sample(int stack_index, TimeStamp time, native_thread_id_t thread_id, Category category) {
|
678
|
+
if (
|
679
|
+
!empty() &&
|
680
|
+
stacks.back() == stack_index &&
|
681
|
+
threads.back() == thread_id &&
|
682
|
+
categories.back() == category)
|
683
|
+
{
|
684
|
+
// We don't compare timestamps for de-duplication
|
685
|
+
weights.back() += 1;
|
686
|
+
} else {
|
687
|
+
stacks.push_back(stack_index);
|
688
|
+
timestamps.push_back(time);
|
689
|
+
threads.push_back(thread_id);
|
690
|
+
categories.push_back(category);
|
691
|
+
weights.push_back(1);
|
692
|
+
}
|
693
|
+
}
|
694
|
+
|
695
|
+
void write_result(VALUE result) const {
|
696
|
+
VALUE samples = rb_ary_new();
|
697
|
+
rb_hash_aset(result, sym("samples"), samples);
|
698
|
+
for (auto& stack_index: this->stacks) {
|
699
|
+
rb_ary_push(samples, INT2NUM(stack_index));
|
700
|
+
}
|
701
|
+
|
702
|
+
VALUE weights = rb_ary_new();
|
703
|
+
rb_hash_aset(result, sym("weights"), weights);
|
704
|
+
for (auto& weight: this->weights) {
|
705
|
+
rb_ary_push(weights, INT2NUM(weight));
|
706
|
+
}
|
707
|
+
|
708
|
+
VALUE timestamps = rb_ary_new();
|
709
|
+
rb_hash_aset(result, sym("timestamps"), timestamps);
|
710
|
+
for (auto& timestamp: this->timestamps) {
|
711
|
+
rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
|
712
|
+
}
|
713
|
+
|
714
|
+
VALUE sample_categories = rb_ary_new();
|
715
|
+
rb_hash_aset(result, sym("sample_categories"), sample_categories);
|
716
|
+
for (auto& cat: this->categories) {
|
717
|
+
rb_ary_push(sample_categories, INT2NUM(cat));
|
718
|
+
}
|
719
|
+
}
|
720
|
+
};
|
721
|
+
|
722
|
+
class Thread {
|
723
|
+
public:
|
724
|
+
SampleList samples;
|
725
|
+
|
726
|
+
enum State {
|
727
|
+
STARTED,
|
728
|
+
RUNNING,
|
729
|
+
READY,
|
730
|
+
SUSPENDED,
|
731
|
+
STOPPED
|
732
|
+
};
|
733
|
+
|
734
|
+
pthread_t pthread_id;
|
735
|
+
native_thread_id_t native_tid;
|
736
|
+
State state;
|
737
|
+
|
738
|
+
TimeStamp state_changed_at;
|
739
|
+
TimeStamp started_at;
|
740
|
+
TimeStamp stopped_at;
|
741
|
+
|
742
|
+
int stack_on_suspend_idx;
|
743
|
+
SampleTranslator translator;
|
744
|
+
|
745
|
+
std::string name;
|
746
|
+
|
747
|
+
Thread(State state) : state(state), stack_on_suspend_idx(-1) {
|
748
|
+
pthread_id = pthread_self();
|
749
|
+
native_tid = get_native_thread_id();
|
750
|
+
started_at = state_changed_at = TimeStamp::Now();
|
751
|
+
}
|
752
|
+
|
753
|
+
void set_state(State new_state, MarkerTable *markers) {
|
754
|
+
if (state == Thread::State::STOPPED) {
|
755
|
+
return;
|
756
|
+
}
|
757
|
+
|
758
|
+
TimeStamp from = state_changed_at;
|
759
|
+
auto now = TimeStamp::Now();
|
760
|
+
|
761
|
+
if (started_at.zero()) {
|
762
|
+
started_at = now;
|
763
|
+
}
|
764
|
+
|
765
|
+
switch (new_state) {
|
766
|
+
case State::STARTED:
|
767
|
+
new_state = State::RUNNING;
|
768
|
+
break;
|
769
|
+
case State::RUNNING:
|
770
|
+
assert(state == State::READY);
|
771
|
+
|
772
|
+
// If the GVL is immediately ready, and we measure no times
|
773
|
+
// stalled, skip emitting the interval.
|
774
|
+
if (from != now) {
|
775
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_STALLED, from, now);
|
776
|
+
}
|
777
|
+
break;
|
778
|
+
case State::READY:
|
779
|
+
// The ready state means "I would like to do some work, but I can't
|
780
|
+
// do it right now either because I blocked on IO and now I want the GVL back,
|
781
|
+
// or because the VM timer put me to sleep"
|
782
|
+
//
|
783
|
+
// Threads can be preempted, which means they will have been in "Running"
|
784
|
+
// state, and then the VM was like "no I need to stop you from working,
|
785
|
+
// so I'll put you in the 'ready' (or stalled) state"
|
786
|
+
assert(state == State::SUSPENDED || state == State::RUNNING);
|
787
|
+
if (state == State::SUSPENDED) {
|
788
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
|
789
|
+
}
|
790
|
+
else {
|
791
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
792
|
+
}
|
793
|
+
break;
|
794
|
+
case State::SUSPENDED:
|
795
|
+
// We can go from RUNNING or STARTED to SUSPENDED
|
796
|
+
assert(state == State::RUNNING || state == State::STARTED);
|
797
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
798
|
+
break;
|
799
|
+
case State::STOPPED:
|
800
|
+
// We can go from RUNNING or STARTED to STOPPED
|
801
|
+
assert(state == State::RUNNING || state == State::STARTED);
|
802
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
803
|
+
stopped_at = now;
|
804
|
+
capture_name();
|
805
|
+
|
806
|
+
break;
|
807
|
+
}
|
808
|
+
|
809
|
+
state = new_state;
|
810
|
+
state_changed_at = now;
|
811
|
+
}
|
812
|
+
|
813
|
+
bool running() {
|
814
|
+
return state != State::STOPPED;
|
815
|
+
}
|
816
|
+
|
817
|
+
void capture_name() {
|
818
|
+
char buf[128];
|
819
|
+
int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
820
|
+
if (rc == 0)
|
821
|
+
name = std::string(buf);
|
822
|
+
}
|
823
|
+
};
|
824
|
+
|
825
|
+
class ThreadTable {
|
826
|
+
public:
|
827
|
+
FrameList &frame_list;
|
828
|
+
|
829
|
+
std::vector<Thread> list;
|
830
|
+
std::mutex mutex;
|
831
|
+
|
832
|
+
ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
|
833
|
+
}
|
834
|
+
|
835
|
+
void started(MarkerTable *markers) {
|
836
|
+
//const std::lock_guard<std::mutex> lock(mutex);
|
837
|
+
|
838
|
+
//list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
|
839
|
+
markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
840
|
+
set_state(Thread::State::STARTED, markers);
|
841
|
+
}
|
842
|
+
|
843
|
+
void ready(MarkerTable *markers) {
|
844
|
+
set_state(Thread::State::READY, markers);
|
845
|
+
}
|
846
|
+
|
847
|
+
void resumed(MarkerTable *markers) {
|
848
|
+
set_state(Thread::State::RUNNING, markers);
|
849
|
+
}
|
850
|
+
|
851
|
+
void suspended(MarkerTable *markers) {
|
852
|
+
set_state(Thread::State::SUSPENDED, markers);
|
853
|
+
}
|
854
|
+
|
855
|
+
void stopped(MarkerTable *markers) {
|
856
|
+
markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
857
|
+
set_state(Thread::State::STOPPED, markers);
|
858
|
+
}
|
859
|
+
|
860
|
+
private:
|
861
|
+
void set_state(Thread::State new_state, MarkerTable *markers) {
|
862
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
863
|
+
|
864
|
+
pthread_t current_thread = pthread_self();
|
865
|
+
//cerr << "set state=" << new_state << " thread=" << gettid() << endl;
|
866
|
+
|
867
|
+
for (auto &thread : list) {
|
868
|
+
if (pthread_equal(current_thread, thread.pthread_id)) {
|
869
|
+
if (new_state == Thread::State::SUSPENDED) {
|
870
|
+
|
871
|
+
RawSample sample;
|
872
|
+
sample.sample();
|
873
|
+
|
874
|
+
thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
|
875
|
+
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
876
|
+
}
|
877
|
+
|
878
|
+
thread.set_state(new_state, markers);
|
879
|
+
|
880
|
+
return;
|
881
|
+
}
|
882
|
+
}
|
883
|
+
|
884
|
+
pid_t native_tid = get_native_thread_id();
|
885
|
+
list.emplace_back(new_state);
|
886
|
+
}
|
887
|
+
};
|
888
|
+
|
515
889
|
class BaseCollector {
|
516
890
|
protected:
|
517
891
|
|
@@ -523,15 +897,19 @@ class BaseCollector {
|
|
523
897
|
bool running = false;
|
524
898
|
FrameList frame_list;
|
525
899
|
|
900
|
+
TimeStamp started_at;
|
901
|
+
|
526
902
|
virtual ~BaseCollector() {}
|
527
903
|
|
528
904
|
virtual bool start() {
|
529
905
|
if (running) {
|
530
906
|
return false;
|
531
|
-
} else {
|
532
|
-
running = true;
|
533
|
-
return true;
|
534
907
|
}
|
908
|
+
|
909
|
+
started_at = TimeStamp::Now();
|
910
|
+
|
911
|
+
running = true;
|
912
|
+
return true;
|
535
913
|
}
|
536
914
|
|
537
915
|
virtual VALUE stop() {
|
@@ -543,6 +921,21 @@ class BaseCollector {
|
|
543
921
|
return Qnil;
|
544
922
|
}
|
545
923
|
|
924
|
+
void write_meta(VALUE result) {
|
925
|
+
VALUE meta = rb_hash_new();
|
926
|
+
rb_ivar_set(result, rb_intern("@meta"), meta);
|
927
|
+
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
928
|
+
|
929
|
+
}
|
930
|
+
|
931
|
+
virtual VALUE build_collector_result() {
|
932
|
+
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
933
|
+
|
934
|
+
write_meta(result);
|
935
|
+
|
936
|
+
return result;
|
937
|
+
}
|
938
|
+
|
546
939
|
virtual void sample() {
|
547
940
|
rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
|
548
941
|
};
|
@@ -557,14 +950,15 @@ class BaseCollector {
|
|
557
950
|
};
|
558
951
|
|
559
952
|
class CustomCollector : public BaseCollector {
|
560
|
-
|
953
|
+
SampleList samples;
|
561
954
|
|
562
955
|
void sample() {
|
563
956
|
RawSample sample;
|
564
957
|
sample.sample();
|
565
958
|
int stack_index = frame_list.stack_index(sample);
|
566
959
|
|
567
|
-
|
960
|
+
native_thread_id_t thread_id = 0;
|
961
|
+
samples.record_sample(stack_index, TimeStamp::Now(), thread_id, CATEGORY_NORMAL);
|
568
962
|
}
|
569
963
|
|
570
964
|
VALUE stop() {
|
@@ -580,17 +974,16 @@ class CustomCollector : public BaseCollector {
|
|
580
974
|
}
|
581
975
|
|
582
976
|
VALUE build_collector_result() {
|
583
|
-
VALUE result =
|
977
|
+
VALUE result = BaseCollector::build_collector_result();
|
584
978
|
|
585
|
-
VALUE
|
586
|
-
rb_ivar_set(result, rb_intern("@
|
587
|
-
VALUE weights = rb_ary_new();
|
588
|
-
rb_ivar_set(result, rb_intern("@weights"), weights);
|
979
|
+
VALUE threads = rb_hash_new();
|
980
|
+
rb_ivar_set(result, rb_intern("@threads"), threads);
|
589
981
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
982
|
+
VALUE thread_hash = rb_hash_new();
|
983
|
+
samples.write_result(thread_hash);
|
984
|
+
|
985
|
+
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
986
|
+
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
594
987
|
|
595
988
|
frame_list.write_result(result);
|
596
989
|
|
@@ -623,16 +1016,18 @@ class RetainedCollector : public BaseCollector {
|
|
623
1016
|
|
624
1017
|
static void newobj_i(VALUE tpval, void *data) {
|
625
1018
|
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
626
|
-
|
1019
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
1020
|
+
VALUE obj = rb_tracearg_object(tparg);
|
627
1021
|
|
628
|
-
collector->record(
|
1022
|
+
collector->record(obj);
|
629
1023
|
}
|
630
1024
|
|
631
1025
|
static void freeobj_i(VALUE tpval, void *data) {
|
632
1026
|
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
633
|
-
|
1027
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
1028
|
+
VALUE obj = rb_tracearg_object(tparg);
|
634
1029
|
|
635
|
-
collector->object_frames.erase(
|
1030
|
+
collector->object_frames.erase(obj);
|
636
1031
|
}
|
637
1032
|
|
638
1033
|
public:
|
@@ -687,12 +1082,21 @@ class RetainedCollector : public BaseCollector {
|
|
687
1082
|
RetainedCollector *collector = this;
|
688
1083
|
FrameList &frame_list = collector->frame_list;
|
689
1084
|
|
690
|
-
VALUE result =
|
1085
|
+
VALUE result = BaseCollector::build_collector_result();
|
1086
|
+
|
1087
|
+
VALUE threads = rb_hash_new();
|
1088
|
+
rb_ivar_set(result, rb_intern("@threads"), threads);
|
1089
|
+
VALUE thread_hash = rb_hash_new();
|
1090
|
+
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
691
1091
|
|
1092
|
+
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
692
1093
|
VALUE samples = rb_ary_new();
|
693
|
-
|
1094
|
+
rb_hash_aset(thread_hash, sym("samples"), samples);
|
694
1095
|
VALUE weights = rb_ary_new();
|
695
|
-
|
1096
|
+
rb_hash_aset(thread_hash, sym("weights"), weights);
|
1097
|
+
|
1098
|
+
rb_hash_aset(thread_hash, sym("name"), rb_str_new_cstr("retained memory"));
|
1099
|
+
rb_hash_aset(thread_hash, sym("started_at"), ULL2NUM(collector->started_at.nanoseconds()));
|
696
1100
|
|
697
1101
|
for (auto& obj: collector->object_list) {
|
698
1102
|
const auto search = collector->object_frames.find(obj);
|
@@ -721,162 +1125,68 @@ class RetainedCollector : public BaseCollector {
|
|
721
1125
|
}
|
722
1126
|
};
|
723
1127
|
|
724
|
-
|
1128
|
+
class GlobalSignalHandler {
|
1129
|
+
static LiveSample *live_sample;
|
725
1130
|
|
726
|
-
class Thread {
|
727
1131
|
public:
|
728
|
-
static
|
729
|
-
|
730
|
-
|
731
|
-
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
732
|
-
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
733
|
-
return thread_id;
|
734
|
-
#else
|
735
|
-
// gettid() is only available as of glibc 2.30
|
736
|
-
pid_t tid = syscall(SYS_gettid);
|
737
|
-
return tid;
|
738
|
-
#endif
|
739
|
-
}
|
740
|
-
|
741
|
-
enum State {
|
742
|
-
STARTED,
|
743
|
-
RUNNING,
|
744
|
-
SUSPENDED,
|
745
|
-
STOPPED
|
746
|
-
};
|
747
|
-
|
748
|
-
pthread_t pthread_id;
|
749
|
-
native_thread_id_t native_tid;
|
750
|
-
State state;
|
751
|
-
|
752
|
-
TimeStamp state_changed_at;
|
753
|
-
TimeStamp started_at;
|
754
|
-
TimeStamp stopped_at;
|
755
|
-
|
756
|
-
RawSample stack_on_suspend;
|
757
|
-
|
758
|
-
std::string name;
|
759
|
-
|
760
|
-
Thread(State state) : state(state) {
|
761
|
-
pthread_id = pthread_self();
|
762
|
-
native_tid = get_native_thread_id();
|
763
|
-
started_at = state_changed_at = TimeStamp::Now();
|
1132
|
+
static GlobalSignalHandler *get_instance() {
|
1133
|
+
static GlobalSignalHandler instance;
|
1134
|
+
return &instance;
|
764
1135
|
}
|
765
1136
|
|
766
|
-
void
|
767
|
-
|
768
|
-
|
769
|
-
}
|
770
|
-
|
771
|
-
auto now = TimeStamp::Now();
|
772
|
-
|
773
|
-
state = new_state;
|
774
|
-
state_changed_at = now;
|
775
|
-
if (new_state == State::STARTED) {
|
776
|
-
if (started_at.zero()) {
|
777
|
-
started_at = now;
|
778
|
-
}
|
779
|
-
} else if (new_state == State::STOPPED) {
|
780
|
-
stopped_at = now;
|
1137
|
+
void install() {
|
1138
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
1139
|
+
count++;
|
781
1140
|
|
782
|
-
|
783
|
-
}
|
1141
|
+
if (count == 1) setup_signal_handler();
|
784
1142
|
}
|
785
1143
|
|
786
|
-
|
787
|
-
|
788
|
-
|
1144
|
+
void uninstall() {
|
1145
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
1146
|
+
count--;
|
789
1147
|
|
790
|
-
|
791
|
-
char buf[128];
|
792
|
-
int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
793
|
-
if (rc == 0)
|
794
|
-
name = std::string(buf);
|
1148
|
+
if (count == 0) clear_signal_handler();
|
795
1149
|
}
|
796
|
-
};
|
797
|
-
|
798
|
-
class Marker {
|
799
|
-
public:
|
800
|
-
enum Type {
|
801
|
-
MARKER_GVL_THREAD_STARTED,
|
802
|
-
MARKER_GVL_THREAD_READY,
|
803
|
-
MARKER_GVL_THREAD_RESUMED,
|
804
|
-
MARKER_GVL_THREAD_SUSPENDED,
|
805
|
-
MARKER_GVL_THREAD_EXITED,
|
806
1150
|
|
807
|
-
|
808
|
-
MARKER_GC_END_MARK,
|
809
|
-
MARKER_GC_END_SWEEP,
|
810
|
-
MARKER_GC_ENTER,
|
811
|
-
MARKER_GC_EXIT,
|
812
|
-
|
813
|
-
MARKER_MAX,
|
814
|
-
};
|
815
|
-
Type type;
|
816
|
-
TimeStamp timestamp;
|
817
|
-
native_thread_id_t thread_id;
|
818
|
-
};
|
819
|
-
|
820
|
-
class MarkerTable {
|
821
|
-
public:
|
822
|
-
std::vector<Marker> list;
|
823
|
-
std::mutex mutex;
|
824
|
-
|
825
|
-
void record(Marker::Type type) {
|
1151
|
+
void record_sample(LiveSample &sample, pthread_t pthread_id) {
|
826
1152
|
const std::lock_guard<std::mutex> lock(mutex);
|
827
1153
|
|
828
|
-
|
1154
|
+
live_sample = &sample;
|
1155
|
+
if (pthread_kill(pthread_id, SIGPROF)) {
|
1156
|
+
rb_bug("pthread_kill failed");
|
1157
|
+
}
|
1158
|
+
sample.wait();
|
1159
|
+
live_sample = NULL;
|
829
1160
|
}
|
830
|
-
};
|
831
1161
|
|
832
|
-
|
833
|
-
|
834
|
-
class ThreadTable {
|
835
|
-
public:
|
836
|
-
std::vector<Thread> list;
|
1162
|
+
private:
|
837
1163
|
std::mutex mutex;
|
1164
|
+
int count;
|
838
1165
|
|
839
|
-
void
|
840
|
-
|
841
|
-
|
842
|
-
//list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
|
843
|
-
set_state(Thread::State::STARTED);
|
1166
|
+
static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
|
1167
|
+
assert(live_sample);
|
1168
|
+
live_sample->sample_current_thread();
|
844
1169
|
}
|
845
1170
|
|
846
|
-
void
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
for (auto &thread : list) {
|
853
|
-
if (pthread_equal(current_thread, thread.pthread_id)) {
|
854
|
-
thread.set_state(new_state);
|
855
|
-
|
856
|
-
if (new_state == Thread::State::SUSPENDED) {
|
857
|
-
thread.stack_on_suspend.sample();
|
858
|
-
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
859
|
-
}
|
860
|
-
return;
|
861
|
-
}
|
862
|
-
}
|
863
|
-
|
864
|
-
pid_t native_tid = Thread::get_native_thread_id();
|
865
|
-
list.emplace_back(new_state);
|
1171
|
+
void setup_signal_handler() {
|
1172
|
+
struct sigaction sa;
|
1173
|
+
sa.sa_sigaction = signal_handler;
|
1174
|
+
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
1175
|
+
sigemptyset(&sa.sa_mask);
|
1176
|
+
sigaction(SIGPROF, &sa, NULL);
|
866
1177
|
}
|
867
|
-
};
|
868
1178
|
|
869
|
-
|
870
|
-
|
871
|
-
|
1179
|
+
void clear_signal_handler() {
|
1180
|
+
struct sigaction sa;
|
1181
|
+
sa.sa_handler = SIG_IGN;
|
1182
|
+
sa.sa_flags = SA_RESTART;
|
1183
|
+
sigemptyset(&sa.sa_mask);
|
1184
|
+
sigaction(SIGPROF, &sa, NULL);
|
1185
|
+
}
|
872
1186
|
};
|
1187
|
+
LiveSample *GlobalSignalHandler::live_sample;
|
873
1188
|
|
874
1189
|
class TimeCollector : public BaseCollector {
|
875
|
-
std::vector<int> samples;
|
876
|
-
std::vector<TimeStamp> timestamps;
|
877
|
-
std::vector<native_thread_id_t> sample_threads;
|
878
|
-
std::vector<Category> sample_categories;
|
879
|
-
|
880
1190
|
MarkerTable markers;
|
881
1191
|
ThreadTable threads;
|
882
1192
|
|
@@ -885,41 +1195,31 @@ class TimeCollector : public BaseCollector {
|
|
885
1195
|
atomic_bool running;
|
886
1196
|
SamplerSemaphore thread_stopped;
|
887
1197
|
|
888
|
-
static LiveSample *live_sample;
|
889
|
-
|
890
|
-
TimeStamp started_at;
|
891
1198
|
TimeStamp interval;
|
892
1199
|
|
893
1200
|
public:
|
894
|
-
TimeCollector(TimeStamp interval) : interval(interval) {
|
1201
|
+
TimeCollector(TimeStamp interval) : interval(interval), threads(frame_list) {
|
895
1202
|
}
|
896
1203
|
|
897
1204
|
private:
|
898
1205
|
|
899
|
-
void record_sample(const RawSample &sample, TimeStamp time,
|
1206
|
+
void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
|
900
1207
|
if (!sample.empty()) {
|
901
|
-
int stack_index =
|
902
|
-
samples.
|
903
|
-
|
904
|
-
|
905
|
-
|
1208
|
+
int stack_index = thread.translator.translate(frame_list, sample);
|
1209
|
+
thread.samples.record_sample(
|
1210
|
+
stack_index,
|
1211
|
+
time,
|
1212
|
+
thread.native_tid,
|
1213
|
+
category
|
1214
|
+
);
|
906
1215
|
}
|
907
1216
|
}
|
908
1217
|
|
909
|
-
static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
|
910
|
-
assert(live_sample);
|
911
|
-
live_sample->sample_current_thread();
|
912
|
-
}
|
913
|
-
|
914
1218
|
VALUE get_markers() {
|
915
|
-
VALUE list =
|
1219
|
+
VALUE list = rb_ary_new2(this->markers.list.size());
|
916
1220
|
|
917
1221
|
for (auto& marker: this->markers.list) {
|
918
|
-
|
919
|
-
record[0] = ULL2NUM(marker.thread_id);
|
920
|
-
record[1] = INT2NUM(marker.type);
|
921
|
-
record[2] = ULL2NUM(marker.timestamp.nanoseconds());
|
922
|
-
rb_ary_push(list, rb_ary_new_from_values(3, record));
|
1222
|
+
rb_ary_push(list, marker.to_array());
|
923
1223
|
}
|
924
1224
|
|
925
1225
|
return list;
|
@@ -927,20 +1227,16 @@ class TimeCollector : public BaseCollector {
|
|
927
1227
|
|
928
1228
|
void sample_thread_run() {
|
929
1229
|
LiveSample sample;
|
930
|
-
live_sample = &sample;
|
931
1230
|
|
932
1231
|
TimeStamp next_sample_schedule = TimeStamp::Now();
|
933
1232
|
while (running) {
|
934
1233
|
TimeStamp sample_start = TimeStamp::Now();
|
935
1234
|
|
936
1235
|
threads.mutex.lock();
|
937
|
-
for (auto thread : threads.list) {
|
1236
|
+
for (auto &thread : threads.list) {
|
938
1237
|
//if (thread.state == Thread::State::RUNNING) {
|
939
|
-
if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.
|
940
|
-
|
941
|
-
rb_bug("pthread_kill failed");
|
942
|
-
}
|
943
|
-
sample.wait();
|
1238
|
+
if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
|
1239
|
+
GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
|
944
1240
|
|
945
1241
|
if (sample.sample.gc) {
|
946
1242
|
// fprintf(stderr, "skipping GC sample\n");
|
@@ -948,27 +1244,29 @@ class TimeCollector : public BaseCollector {
|
|
948
1244
|
record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
|
949
1245
|
}
|
950
1246
|
} else if (thread.state == Thread::State::SUSPENDED) {
|
951
|
-
record_sample(
|
1247
|
+
thread.samples.record_sample(
|
1248
|
+
thread.stack_on_suspend_idx,
|
1249
|
+
sample_start,
|
1250
|
+
thread.native_tid,
|
1251
|
+
CATEGORY_IDLE);
|
952
1252
|
} else {
|
953
1253
|
}
|
954
1254
|
}
|
1255
|
+
|
955
1256
|
threads.mutex.unlock();
|
956
1257
|
|
957
1258
|
TimeStamp sample_complete = TimeStamp::Now();
|
958
1259
|
|
959
1260
|
next_sample_schedule += interval;
|
960
1261
|
|
1262
|
+
// If sampling falls behind, restart, and check in another interval
|
961
1263
|
if (next_sample_schedule < sample_complete) {
|
962
|
-
//fprintf(stderr, "fell behind by %ius\n", (sample_complete - next_sample_schedule).microseconds());
|
963
1264
|
next_sample_schedule = sample_complete + interval;
|
964
1265
|
}
|
965
1266
|
|
966
|
-
TimeStamp
|
967
|
-
TimeStamp::Sleep(sleep_time);
|
1267
|
+
TimeStamp::SleepUntil(next_sample_schedule);
|
968
1268
|
}
|
969
1269
|
|
970
|
-
live_sample = NULL;
|
971
|
-
|
972
1270
|
thread_stopped.post();
|
973
1271
|
}
|
974
1272
|
|
@@ -978,10 +1276,21 @@ class TimeCollector : public BaseCollector {
|
|
978
1276
|
return NULL;
|
979
1277
|
}
|
980
1278
|
|
981
|
-
static void
|
982
|
-
TimeCollector *collector = static_cast<TimeCollector *>(data);
|
983
|
-
|
984
|
-
|
1279
|
+
static void internal_thread_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
|
1280
|
+
TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
|
1281
|
+
|
1282
|
+
switch (event) {
|
1283
|
+
case RUBY_EVENT_THREAD_BEGIN:
|
1284
|
+
collector->threads.started(&collector->markers);
|
1285
|
+
break;
|
1286
|
+
case RUBY_EVENT_THREAD_END:
|
1287
|
+
collector->threads.stopped(&collector->markers);
|
1288
|
+
break;
|
1289
|
+
}
|
1290
|
+
}
|
1291
|
+
|
1292
|
+
static void internal_gc_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
|
1293
|
+
TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
|
985
1294
|
|
986
1295
|
switch (event) {
|
987
1296
|
case RUBY_INTERNAL_EVENT_GC_START:
|
@@ -994,10 +1303,10 @@ class TimeCollector : public BaseCollector {
|
|
994
1303
|
collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
|
995
1304
|
break;
|
996
1305
|
case RUBY_INTERNAL_EVENT_GC_ENTER:
|
997
|
-
collector->markers.
|
1306
|
+
collector->markers.record_gc_entered();
|
998
1307
|
break;
|
999
1308
|
case RUBY_INTERNAL_EVENT_GC_EXIT:
|
1000
|
-
collector->markers.
|
1309
|
+
collector->markers.record_gc_leave();
|
1001
1310
|
break;
|
1002
1311
|
}
|
1003
1312
|
}
|
@@ -1007,44 +1316,27 @@ class TimeCollector : public BaseCollector {
|
|
1007
1316
|
//cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
|
1008
1317
|
|
1009
1318
|
switch (event) {
|
1010
|
-
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
1011
|
-
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
1012
|
-
collector->threads.started();
|
1013
|
-
break;
|
1014
1319
|
case RUBY_INTERNAL_THREAD_EVENT_READY:
|
1015
|
-
collector->
|
1320
|
+
collector->threads.ready(&collector->markers);
|
1016
1321
|
break;
|
1017
1322
|
case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
|
1018
|
-
collector->
|
1019
|
-
collector->threads.set_state(Thread::State::RUNNING);
|
1323
|
+
collector->threads.resumed(&collector->markers);
|
1020
1324
|
break;
|
1021
1325
|
case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
|
1022
|
-
collector->
|
1023
|
-
collector->threads.set_state(Thread::State::SUSPENDED);
|
1024
|
-
break;
|
1025
|
-
case RUBY_INTERNAL_THREAD_EVENT_EXITED:
|
1026
|
-
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
1027
|
-
collector->threads.set_state(Thread::State::STOPPED);
|
1326
|
+
collector->threads.suspended(&collector->markers);
|
1028
1327
|
break;
|
1029
1328
|
|
1030
1329
|
}
|
1031
1330
|
}
|
1032
1331
|
|
1033
1332
|
rb_internal_thread_event_hook_t *thread_hook;
|
1034
|
-
VALUE gc_hook;
|
1035
1333
|
|
1036
1334
|
bool start() {
|
1037
1335
|
if (!BaseCollector::start()) {
|
1038
1336
|
return false;
|
1039
1337
|
}
|
1040
1338
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
struct sigaction sa;
|
1044
|
-
sa.sa_sigaction = signal_handler;
|
1045
|
-
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
1046
|
-
sigemptyset(&sa.sa_mask);
|
1047
|
-
sigaction(SIGPROF, &sa, NULL);
|
1339
|
+
GlobalSignalHandler::get_instance()->install();
|
1048
1340
|
|
1049
1341
|
running = true;
|
1050
1342
|
|
@@ -1054,15 +1346,16 @@ class TimeCollector : public BaseCollector {
|
|
1054
1346
|
rb_bug("pthread_create");
|
1055
1347
|
}
|
1056
1348
|
|
1057
|
-
// Set the state of the current Ruby thread to RUNNING
|
1058
|
-
//
|
1059
|
-
//
|
1060
|
-
//
|
1061
|
-
|
1349
|
+
// Set the state of the current Ruby thread to RUNNING, which we know it
|
1350
|
+
// is as it must have held the GVL to start the collector. We want to
|
1351
|
+
// have at least one thread in our thread list because it's possible
|
1352
|
+
// that the profile might be such that we don't get any thread switch
|
1353
|
+
// events and we need at least one
|
1354
|
+
this->threads.resumed(&this->markers);
|
1062
1355
|
|
1063
1356
|
thread_hook = rb_internal_thread_add_event_hook(internal_thread_event_cb, RUBY_INTERNAL_THREAD_EVENT_MASK, this);
|
1064
|
-
|
1065
|
-
|
1357
|
+
rb_add_event_hook(internal_gc_event_cb, RUBY_INTERNAL_EVENTS, PTR2NUM((void *)this));
|
1358
|
+
rb_add_event_hook(internal_thread_event_cb, RUBY_NORMAL_EVENTS, PTR2NUM((void *)this));
|
1066
1359
|
|
1067
1360
|
return true;
|
1068
1361
|
}
|
@@ -1073,14 +1366,11 @@ class TimeCollector : public BaseCollector {
|
|
1073
1366
|
running = false;
|
1074
1367
|
thread_stopped.wait();
|
1075
1368
|
|
1076
|
-
|
1077
|
-
sa.sa_handler = SIG_IGN;
|
1078
|
-
sa.sa_flags = SA_RESTART;
|
1079
|
-
sigemptyset(&sa.sa_mask);
|
1080
|
-
sigaction(SIGPROF, &sa, NULL);
|
1369
|
+
GlobalSignalHandler::get_instance()->uninstall();
|
1081
1370
|
|
1082
1371
|
rb_internal_thread_remove_event_hook(thread_hook);
|
1083
|
-
|
1372
|
+
rb_remove_event_hook(internal_gc_event_cb);
|
1373
|
+
rb_remove_event_hook(internal_thread_event_cb);
|
1084
1374
|
|
1085
1375
|
// capture thread names
|
1086
1376
|
for (auto& thread: this->threads.list) {
|
@@ -1099,45 +1389,15 @@ class TimeCollector : public BaseCollector {
|
|
1099
1389
|
}
|
1100
1390
|
|
1101
1391
|
VALUE build_collector_result() {
|
1102
|
-
VALUE result =
|
1103
|
-
|
1104
|
-
VALUE meta = rb_hash_new();
|
1105
|
-
rb_ivar_set(result, rb_intern("@meta"), meta);
|
1106
|
-
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
1107
|
-
|
1108
|
-
VALUE samples = rb_ary_new();
|
1109
|
-
rb_ivar_set(result, rb_intern("@samples"), samples);
|
1110
|
-
VALUE weights = rb_ary_new();
|
1111
|
-
rb_ivar_set(result, rb_intern("@weights"), weights);
|
1112
|
-
for (auto& stack_index: this->samples) {
|
1113
|
-
rb_ary_push(samples, INT2NUM(stack_index));
|
1114
|
-
rb_ary_push(weights, INT2NUM(1));
|
1115
|
-
}
|
1116
|
-
|
1117
|
-
VALUE timestamps = rb_ary_new();
|
1118
|
-
rb_ivar_set(result, rb_intern("@timestamps"), timestamps);
|
1119
|
-
|
1120
|
-
for (auto& timestamp: this->timestamps) {
|
1121
|
-
rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
|
1122
|
-
}
|
1123
|
-
|
1124
|
-
VALUE sample_threads = rb_ary_new();
|
1125
|
-
rb_ivar_set(result, rb_intern("@sample_threads"), sample_threads);
|
1126
|
-
for (auto& thread: this->sample_threads) {
|
1127
|
-
rb_ary_push(sample_threads, ULL2NUM(thread));
|
1128
|
-
}
|
1129
|
-
|
1130
|
-
VALUE sample_categories = rb_ary_new();
|
1131
|
-
rb_ivar_set(result, rb_intern("@sample_categories"), sample_categories);
|
1132
|
-
for (auto& cat: this->sample_categories) {
|
1133
|
-
rb_ary_push(sample_categories, INT2NUM(cat));
|
1134
|
-
}
|
1392
|
+
VALUE result = BaseCollector::build_collector_result();
|
1135
1393
|
|
1136
1394
|
VALUE threads = rb_hash_new();
|
1137
1395
|
rb_ivar_set(result, rb_intern("@threads"), threads);
|
1138
1396
|
|
1139
1397
|
for (const auto& thread: this->threads.list) {
|
1140
1398
|
VALUE hash = rb_hash_new();
|
1399
|
+
thread.samples.write_result(hash);
|
1400
|
+
|
1141
1401
|
rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
|
1142
1402
|
rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
|
1143
1403
|
rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
|
@@ -1155,7 +1415,6 @@ class TimeCollector : public BaseCollector {
|
|
1155
1415
|
|
1156
1416
|
void mark() {
|
1157
1417
|
frame_list.mark_frames();
|
1158
|
-
rb_gc_mark(gc_hook);
|
1159
1418
|
|
1160
1419
|
//for (int i = 0; i < queued_length; i++) {
|
1161
1420
|
// rb_gc_mark(queued_frames[i]);
|
@@ -1165,8 +1424,6 @@ class TimeCollector : public BaseCollector {
|
|
1165
1424
|
}
|
1166
1425
|
};
|
1167
1426
|
|
1168
|
-
LiveSample *TimeCollector::live_sample;
|
1169
|
-
|
1170
1427
|
static void
|
1171
1428
|
collector_mark(void *data) {
|
1172
1429
|
BaseCollector *collector = static_cast<BaseCollector *>(data);
|
@@ -1252,14 +1509,11 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
1252
1509
|
}
|
1253
1510
|
|
1254
1511
|
static void
|
1255
|
-
Init_consts() {
|
1512
|
+
Init_consts(VALUE rb_mVernierMarkerPhase) {
|
1256
1513
|
#define MARKER_CONST(name) \
|
1257
1514
|
rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
|
1258
1515
|
|
1259
1516
|
MARKER_CONST(GVL_THREAD_STARTED);
|
1260
|
-
MARKER_CONST(GVL_THREAD_READY);
|
1261
|
-
MARKER_CONST(GVL_THREAD_RESUMED);
|
1262
|
-
MARKER_CONST(GVL_THREAD_SUSPENDED);
|
1263
1517
|
MARKER_CONST(GVL_THREAD_EXITED);
|
1264
1518
|
|
1265
1519
|
MARKER_CONST(GC_START);
|
@@ -1267,8 +1521,22 @@ Init_consts() {
|
|
1267
1521
|
MARKER_CONST(GC_END_SWEEP);
|
1268
1522
|
MARKER_CONST(GC_ENTER);
|
1269
1523
|
MARKER_CONST(GC_EXIT);
|
1524
|
+
MARKER_CONST(GC_PAUSE);
|
1525
|
+
|
1526
|
+
MARKER_CONST(THREAD_RUNNING);
|
1527
|
+
MARKER_CONST(THREAD_STALLED);
|
1528
|
+
MARKER_CONST(THREAD_SUSPENDED);
|
1270
1529
|
|
1271
1530
|
#undef MARKER_CONST
|
1531
|
+
|
1532
|
+
#define PHASE_CONST(name) \
|
1533
|
+
rb_define_const(rb_mVernierMarkerPhase, #name, INT2NUM(Marker::Phase::name))
|
1534
|
+
|
1535
|
+
PHASE_CONST(INSTANT);
|
1536
|
+
PHASE_CONST(INTERVAL);
|
1537
|
+
PHASE_CONST(INTERVAL_START);
|
1538
|
+
PHASE_CONST(INTERVAL_END);
|
1539
|
+
#undef PHASE_CONST
|
1272
1540
|
}
|
1273
1541
|
|
1274
1542
|
extern "C" void
|
@@ -1277,6 +1545,7 @@ Init_vernier(void)
|
|
1277
1545
|
rb_mVernier = rb_define_module("Vernier");
|
1278
1546
|
rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
|
1279
1547
|
VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
|
1548
|
+
VALUE rb_mVernierMarkerPhase = rb_define_module_under(rb_mVernierMarker, "Phase");
|
1280
1549
|
rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
|
1281
1550
|
|
1282
1551
|
rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
|
@@ -1287,7 +1556,7 @@ Init_vernier(void)
|
|
1287
1556
|
rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
|
1288
1557
|
rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
|
1289
1558
|
|
1290
|
-
Init_consts();
|
1559
|
+
Init_consts(rb_mVernierMarkerPhase);
|
1291
1560
|
|
1292
1561
|
//static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
|
1293
1562
|
//rb_global_variable(&gc_hook);
|