vernier 0.2.0 → 0.3.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/README.md +2 -2
- 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/extconf.rb +1 -1
- data/ext/vernier/vernier.cc +626 -293
- 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 +74 -41
- data/lib/vernier/result.rb +139 -0
- data/lib/vernier/version.rb +1 -1
- data/lib/vernier.rb +3 -130
- metadata +11 -5
- data/ext/vernier/stack.hh +0 -155
- data/sig/vernier.rbs +0 -4
data/ext/vernier/vernier.cc
CHANGED
@@ -9,31 +9,38 @@
|
|
9
9
|
#include <cassert>
|
10
10
|
#include <atomic>
|
11
11
|
#include <mutex>
|
12
|
-
#include <optional>
|
13
12
|
|
14
13
|
#include <sys/time.h>
|
15
14
|
#include <signal.h>
|
16
15
|
#ifdef __APPLE__
|
16
|
+
/* macOS */
|
17
17
|
#include <dispatch/dispatch.h>
|
18
18
|
#else
|
19
|
+
/* Linux */
|
19
20
|
#include <semaphore.h>
|
21
|
+
#include <sys/syscall.h> /* for SYS_gettid */
|
20
22
|
#endif
|
21
23
|
|
22
24
|
#include "vernier.hh"
|
23
|
-
#include "stack.hh"
|
24
25
|
|
25
26
|
#include "ruby/ruby.h"
|
26
27
|
#include "ruby/debug.h"
|
27
28
|
#include "ruby/thread.h"
|
28
29
|
|
29
|
-
|
30
|
-
|
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 \
|
31
34
|
RUBY_INTERNAL_EVENT_GC_START | \
|
32
35
|
RUBY_INTERNAL_EVENT_GC_END_MARK | \
|
33
36
|
RUBY_INTERNAL_EVENT_GC_END_SWEEP | \
|
34
37
|
RUBY_INTERNAL_EVENT_GC_ENTER | \
|
35
38
|
RUBY_INTERNAL_EVENT_GC_EXIT
|
36
39
|
|
40
|
+
#define RUBY_NORMAL_EVENTS \
|
41
|
+
RUBY_EVENT_THREAD_BEGIN | \
|
42
|
+
RUBY_EVENT_THREAD_END
|
43
|
+
|
37
44
|
#define sym(name) ID2SYM(rb_intern_const(name))
|
38
45
|
|
39
46
|
// HACK: This isn't public, but the objspace ext uses it
|
@@ -124,6 +131,14 @@ class TimeStamp {
|
|
124
131
|
return value_ns >= other.value_ns;
|
125
132
|
}
|
126
133
|
|
134
|
+
bool operator==(const TimeStamp &other) const {
|
135
|
+
return value_ns == other.value_ns;
|
136
|
+
}
|
137
|
+
|
138
|
+
bool operator!=(const TimeStamp &other) const {
|
139
|
+
return value_ns != other.value_ns;
|
140
|
+
}
|
141
|
+
|
127
142
|
uint64_t nanoseconds() const {
|
128
143
|
return value_ns;
|
129
144
|
}
|
@@ -149,6 +164,73 @@ std::ostream& operator<<(std::ostream& os, const TimeStamp& info) {
|
|
149
164
|
return os;
|
150
165
|
}
|
151
166
|
|
167
|
+
struct FrameInfo {
|
168
|
+
static const char *label_cstr(VALUE frame) {
|
169
|
+
VALUE label = rb_profile_frame_full_label(frame);
|
170
|
+
return StringValueCStr(label);
|
171
|
+
}
|
172
|
+
|
173
|
+
static const char *file_cstr(VALUE frame) {
|
174
|
+
VALUE file = rb_profile_frame_absolute_path(frame);
|
175
|
+
if (NIL_P(file))
|
176
|
+
file = rb_profile_frame_path(frame);
|
177
|
+
if (NIL_P(file)) {
|
178
|
+
return "";
|
179
|
+
} else {
|
180
|
+
return StringValueCStr(file);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
static int first_lineno_int(VALUE frame) {
|
185
|
+
VALUE first_lineno = rb_profile_frame_first_lineno(frame);
|
186
|
+
return NIL_P(first_lineno) ? 0 : FIX2INT(first_lineno);
|
187
|
+
}
|
188
|
+
|
189
|
+
FrameInfo(VALUE frame) :
|
190
|
+
label(label_cstr(frame)),
|
191
|
+
file(file_cstr(frame)),
|
192
|
+
first_lineno(first_lineno_int(frame)) { }
|
193
|
+
|
194
|
+
std::string label;
|
195
|
+
std::string file;
|
196
|
+
int first_lineno;
|
197
|
+
};
|
198
|
+
|
199
|
+
bool operator==(const FrameInfo& lhs, const FrameInfo& rhs) noexcept {
|
200
|
+
return
|
201
|
+
lhs.label == rhs.label &&
|
202
|
+
lhs.file == rhs.file &&
|
203
|
+
lhs.first_lineno == rhs.first_lineno;
|
204
|
+
}
|
205
|
+
|
206
|
+
struct Frame {
|
207
|
+
VALUE frame;
|
208
|
+
int line;
|
209
|
+
|
210
|
+
FrameInfo info() const {
|
211
|
+
return FrameInfo(frame);
|
212
|
+
}
|
213
|
+
};
|
214
|
+
|
215
|
+
bool operator==(const Frame& lhs, const Frame& rhs) noexcept {
|
216
|
+
return lhs.frame == rhs.frame && lhs.line == rhs.line;
|
217
|
+
}
|
218
|
+
|
219
|
+
bool operator!=(const Frame& lhs, const Frame& rhs) noexcept {
|
220
|
+
return !(lhs == rhs);
|
221
|
+
}
|
222
|
+
|
223
|
+
namespace std {
|
224
|
+
template<>
|
225
|
+
struct hash<Frame>
|
226
|
+
{
|
227
|
+
std::size_t operator()(Frame const& s) const noexcept
|
228
|
+
{
|
229
|
+
return s.frame ^ s.line;
|
230
|
+
}
|
231
|
+
};
|
232
|
+
}
|
233
|
+
|
152
234
|
// A basic semaphore built on sem_wait/sem_post
|
153
235
|
// post() is guaranteed to be async-signal-safe
|
154
236
|
class SamplerSemaphore {
|
@@ -211,7 +293,9 @@ struct RawSample {
|
|
211
293
|
}
|
212
294
|
|
213
295
|
Frame frame(int i) const {
|
214
|
-
|
296
|
+
int idx = len - i - 1;
|
297
|
+
if (idx < 0) throw std::out_of_range("out of range");
|
298
|
+
const Frame frame = {frames[idx], lines[idx]};
|
215
299
|
return frame;
|
216
300
|
}
|
217
301
|
|
@@ -273,24 +357,6 @@ struct LiveSample {
|
|
273
357
|
}
|
274
358
|
};
|
275
359
|
|
276
|
-
struct TraceArg {
|
277
|
-
rb_trace_arg_t *tparg;
|
278
|
-
VALUE obj;
|
279
|
-
VALUE path;
|
280
|
-
VALUE line;
|
281
|
-
VALUE mid;
|
282
|
-
VALUE klass;
|
283
|
-
|
284
|
-
TraceArg(VALUE tpval) {
|
285
|
-
tparg = rb_tracearg_from_tracepoint(tpval);
|
286
|
-
obj = rb_tracearg_object(tparg);
|
287
|
-
path = rb_tracearg_path(tparg);
|
288
|
-
line = rb_tracearg_lineno(tparg);
|
289
|
-
mid = rb_tracearg_method_id(tparg);
|
290
|
-
klass = rb_tracearg_defined_class(tparg);
|
291
|
-
}
|
292
|
-
};
|
293
|
-
|
294
360
|
struct FrameList {
|
295
361
|
std::unordered_map<std::string, int> string_to_idx;
|
296
362
|
std::vector<std::string> string_list;
|
@@ -348,27 +414,29 @@ struct FrameList {
|
|
348
414
|
}
|
349
415
|
|
350
416
|
StackNode *node = &root_stack_node;
|
351
|
-
for (int i = stack.size()
|
352
|
-
|
417
|
+
for (int i = 0; i < stack.size(); i++) {
|
418
|
+
Frame frame = stack.frame(i);
|
353
419
|
node = next_stack_node(node, frame);
|
354
420
|
}
|
355
421
|
return node->index;
|
356
422
|
}
|
357
423
|
|
358
|
-
StackNode *next_stack_node(StackNode *node,
|
359
|
-
|
360
|
-
if (
|
424
|
+
StackNode *next_stack_node(StackNode *node, Frame frame) {
|
425
|
+
auto search = node->children.find(frame);
|
426
|
+
if (search == node->children.end()) {
|
361
427
|
// insert a new node
|
362
|
-
next_node_idx = stack_node_list.size();
|
428
|
+
int next_node_idx = stack_node_list.size();
|
363
429
|
node->children[frame] = next_node_idx;
|
364
430
|
stack_node_list.emplace_back(
|
365
431
|
frame,
|
366
432
|
next_node_idx,
|
367
433
|
node->index
|
368
434
|
);
|
435
|
+
return &stack_node_list[next_node_idx];
|
436
|
+
} else {
|
437
|
+
int node_idx = search->second;
|
438
|
+
return &stack_node_list[node_idx];
|
369
439
|
}
|
370
|
-
|
371
|
-
return &stack_node_list[next_node_idx];
|
372
440
|
}
|
373
441
|
|
374
442
|
// Converts Frames from stacks other tables. "Symbolicates" the frames
|
@@ -448,6 +516,372 @@ struct FrameList {
|
|
448
516
|
}
|
449
517
|
};
|
450
518
|
|
519
|
+
class SampleTranslator {
|
520
|
+
public:
|
521
|
+
int last_stack_index;
|
522
|
+
|
523
|
+
Frame frames[RawSample::MAX_LEN];
|
524
|
+
int frame_indexes[RawSample::MAX_LEN];
|
525
|
+
int len;
|
526
|
+
|
527
|
+
SampleTranslator() : len(0), last_stack_index(-1) {
|
528
|
+
}
|
529
|
+
|
530
|
+
int translate(FrameList &frame_list, const RawSample &sample) {
|
531
|
+
int i = 0;
|
532
|
+
for (; i < len && i < sample.size(); i++) {
|
533
|
+
if (frames[i] != sample.frame(i)) {
|
534
|
+
break;
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
FrameList::StackNode *node = i == 0 ? &frame_list.root_stack_node : &frame_list.stack_node_list[frame_indexes[i - 1]];
|
539
|
+
|
540
|
+
for (; i < sample.size(); i++) {
|
541
|
+
Frame frame = sample.frame(i);
|
542
|
+
node = frame_list.next_stack_node(node, frame);
|
543
|
+
|
544
|
+
frames[i] = frame;
|
545
|
+
frame_indexes[i] = node->index;
|
546
|
+
}
|
547
|
+
len = i;
|
548
|
+
|
549
|
+
last_stack_index = node->index;
|
550
|
+
return last_stack_index;
|
551
|
+
}
|
552
|
+
};
|
553
|
+
|
554
|
+
typedef uint64_t native_thread_id_t;
|
555
|
+
static native_thread_id_t get_native_thread_id() {
|
556
|
+
#ifdef __APPLE__
|
557
|
+
uint64_t thread_id;
|
558
|
+
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
559
|
+
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
560
|
+
return thread_id;
|
561
|
+
#else
|
562
|
+
// gettid() is only available as of glibc 2.30
|
563
|
+
pid_t tid = syscall(SYS_gettid);
|
564
|
+
return tid;
|
565
|
+
#endif
|
566
|
+
}
|
567
|
+
|
568
|
+
|
569
|
+
class Marker {
|
570
|
+
public:
|
571
|
+
enum Type {
|
572
|
+
MARKER_GVL_THREAD_STARTED,
|
573
|
+
MARKER_GVL_THREAD_EXITED,
|
574
|
+
|
575
|
+
MARKER_GC_START,
|
576
|
+
MARKER_GC_END_MARK,
|
577
|
+
MARKER_GC_END_SWEEP,
|
578
|
+
MARKER_GC_ENTER,
|
579
|
+
MARKER_GC_EXIT,
|
580
|
+
MARKER_GC_PAUSE,
|
581
|
+
|
582
|
+
MARKER_THREAD_RUNNING,
|
583
|
+
MARKER_THREAD_STALLED,
|
584
|
+
MARKER_THREAD_SUSPENDED,
|
585
|
+
|
586
|
+
MARKER_MAX,
|
587
|
+
};
|
588
|
+
|
589
|
+
// Must match phase types from Gecko
|
590
|
+
enum Phase {
|
591
|
+
INSTANT,
|
592
|
+
INTERVAL,
|
593
|
+
INTERVAL_START,
|
594
|
+
INTERVAL_END
|
595
|
+
};
|
596
|
+
|
597
|
+
Type type;
|
598
|
+
Phase phase;
|
599
|
+
TimeStamp timestamp;
|
600
|
+
TimeStamp finish;
|
601
|
+
native_thread_id_t thread_id;
|
602
|
+
int stack_index = -1;
|
603
|
+
|
604
|
+
VALUE to_array() {
|
605
|
+
VALUE record[6] = {0};
|
606
|
+
record[0] = ULL2NUM(thread_id);
|
607
|
+
record[1] = INT2NUM(type);
|
608
|
+
record[2] = INT2NUM(phase);
|
609
|
+
record[3] = ULL2NUM(timestamp.nanoseconds());
|
610
|
+
|
611
|
+
if (phase == Marker::Phase::INTERVAL) {
|
612
|
+
record[4] = ULL2NUM(finish.nanoseconds());
|
613
|
+
}
|
614
|
+
else {
|
615
|
+
record[4] = Qnil;
|
616
|
+
}
|
617
|
+
record[5] = stack_index == -1 ? Qnil : INT2NUM(stack_index);
|
618
|
+
|
619
|
+
return rb_ary_new_from_values(6, record);
|
620
|
+
}
|
621
|
+
};
|
622
|
+
|
623
|
+
class MarkerTable {
|
624
|
+
TimeStamp last_gc_entry;
|
625
|
+
|
626
|
+
public:
|
627
|
+
std::vector<Marker> list;
|
628
|
+
std::mutex mutex;
|
629
|
+
|
630
|
+
void record_gc_entered() {
|
631
|
+
last_gc_entry = TimeStamp::Now();
|
632
|
+
}
|
633
|
+
|
634
|
+
void record_gc_leave() {
|
635
|
+
list.push_back({ Marker::MARKER_GC_PAUSE, Marker::INTERVAL, last_gc_entry, TimeStamp::Now(), get_native_thread_id(), -1 });
|
636
|
+
}
|
637
|
+
|
638
|
+
void record_interval(Marker::Type type, TimeStamp from, TimeStamp to, int stack_index = -1) {
|
639
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
640
|
+
|
641
|
+
list.push_back({ type, Marker::INTERVAL, from, to, get_native_thread_id(), stack_index });
|
642
|
+
}
|
643
|
+
|
644
|
+
void record(Marker::Type type, int stack_index = -1) {
|
645
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
646
|
+
|
647
|
+
list.push_back({ type, Marker::INSTANT, TimeStamp::Now(), TimeStamp(), get_native_thread_id(), stack_index });
|
648
|
+
}
|
649
|
+
};
|
650
|
+
|
651
|
+
enum Category{
|
652
|
+
CATEGORY_NORMAL,
|
653
|
+
CATEGORY_IDLE
|
654
|
+
};
|
655
|
+
|
656
|
+
class SampleList {
|
657
|
+
public:
|
658
|
+
|
659
|
+
std::vector<int> stacks;
|
660
|
+
std::vector<TimeStamp> timestamps;
|
661
|
+
std::vector<native_thread_id_t> threads;
|
662
|
+
std::vector<Category> categories;
|
663
|
+
std::vector<int> weights;
|
664
|
+
|
665
|
+
size_t size() {
|
666
|
+
return stacks.size();
|
667
|
+
}
|
668
|
+
|
669
|
+
bool empty() {
|
670
|
+
return size() == 0;
|
671
|
+
}
|
672
|
+
|
673
|
+
void record_sample(int stack_index, TimeStamp time, native_thread_id_t thread_id, Category category) {
|
674
|
+
if (
|
675
|
+
!empty() &&
|
676
|
+
stacks.back() == stack_index &&
|
677
|
+
threads.back() == thread_id &&
|
678
|
+
categories.back() == category)
|
679
|
+
{
|
680
|
+
// We don't compare timestamps for de-duplication
|
681
|
+
weights.back() += 1;
|
682
|
+
} else {
|
683
|
+
stacks.push_back(stack_index);
|
684
|
+
timestamps.push_back(time);
|
685
|
+
threads.push_back(thread_id);
|
686
|
+
categories.push_back(category);
|
687
|
+
weights.push_back(1);
|
688
|
+
}
|
689
|
+
}
|
690
|
+
|
691
|
+
void write_result(VALUE result) const {
|
692
|
+
VALUE samples = rb_ary_new();
|
693
|
+
rb_hash_aset(result, sym("samples"), samples);
|
694
|
+
for (auto& stack_index: this->stacks) {
|
695
|
+
rb_ary_push(samples, INT2NUM(stack_index));
|
696
|
+
}
|
697
|
+
|
698
|
+
VALUE weights = rb_ary_new();
|
699
|
+
rb_hash_aset(result, sym("weights"), weights);
|
700
|
+
for (auto& weight: this->weights) {
|
701
|
+
rb_ary_push(weights, INT2NUM(weight));
|
702
|
+
}
|
703
|
+
|
704
|
+
VALUE timestamps = rb_ary_new();
|
705
|
+
rb_hash_aset(result, sym("timestamps"), timestamps);
|
706
|
+
for (auto& timestamp: this->timestamps) {
|
707
|
+
rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
|
708
|
+
}
|
709
|
+
|
710
|
+
VALUE sample_categories = rb_ary_new();
|
711
|
+
rb_hash_aset(result, sym("sample_categories"), sample_categories);
|
712
|
+
for (auto& cat: this->categories) {
|
713
|
+
rb_ary_push(sample_categories, INT2NUM(cat));
|
714
|
+
}
|
715
|
+
}
|
716
|
+
};
|
717
|
+
|
718
|
+
class Thread {
|
719
|
+
public:
|
720
|
+
SampleList samples;
|
721
|
+
|
722
|
+
enum State {
|
723
|
+
STARTED,
|
724
|
+
RUNNING,
|
725
|
+
READY,
|
726
|
+
SUSPENDED,
|
727
|
+
STOPPED
|
728
|
+
};
|
729
|
+
|
730
|
+
pthread_t pthread_id;
|
731
|
+
native_thread_id_t native_tid;
|
732
|
+
State state;
|
733
|
+
|
734
|
+
TimeStamp state_changed_at;
|
735
|
+
TimeStamp started_at;
|
736
|
+
TimeStamp stopped_at;
|
737
|
+
|
738
|
+
int stack_on_suspend_idx;
|
739
|
+
SampleTranslator translator;
|
740
|
+
|
741
|
+
std::string name;
|
742
|
+
|
743
|
+
Thread(State state) : state(state), stack_on_suspend_idx(-1) {
|
744
|
+
pthread_id = pthread_self();
|
745
|
+
native_tid = get_native_thread_id();
|
746
|
+
started_at = state_changed_at = TimeStamp::Now();
|
747
|
+
}
|
748
|
+
|
749
|
+
void set_state(State new_state, MarkerTable *markers) {
|
750
|
+
if (state == Thread::State::STOPPED) {
|
751
|
+
return;
|
752
|
+
}
|
753
|
+
|
754
|
+
TimeStamp from = state_changed_at;
|
755
|
+
auto now = TimeStamp::Now();
|
756
|
+
|
757
|
+
if (started_at.zero()) {
|
758
|
+
started_at = now;
|
759
|
+
}
|
760
|
+
|
761
|
+
switch (new_state) {
|
762
|
+
case State::STARTED:
|
763
|
+
new_state = State::RUNNING;
|
764
|
+
break;
|
765
|
+
case State::RUNNING:
|
766
|
+
assert(state == State::READY);
|
767
|
+
|
768
|
+
// If the GVL is immediately ready, and we measure no times
|
769
|
+
// stalled, skip emitting the interval.
|
770
|
+
if (from != now) {
|
771
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_STALLED, from, now);
|
772
|
+
}
|
773
|
+
break;
|
774
|
+
case State::READY:
|
775
|
+
// The ready state means "I would like to do some work, but I can't
|
776
|
+
// do it right now either because I blocked on IO and now I want the GVL back,
|
777
|
+
// or because the VM timer put me to sleep"
|
778
|
+
//
|
779
|
+
// Threads can be preempted, which means they will have been in "Running"
|
780
|
+
// state, and then the VM was like "no I need to stop you from working,
|
781
|
+
// so I'll put you in the 'ready' (or stalled) state"
|
782
|
+
assert(state == State::SUSPENDED || state == State::RUNNING);
|
783
|
+
if (state == State::SUSPENDED) {
|
784
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_SUSPENDED, from, now, stack_on_suspend_idx);
|
785
|
+
}
|
786
|
+
else {
|
787
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
788
|
+
}
|
789
|
+
break;
|
790
|
+
case State::SUSPENDED:
|
791
|
+
// We can go from RUNNING or STARTED to SUSPENDED
|
792
|
+
assert(state == State::RUNNING || state == State::STARTED);
|
793
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
794
|
+
break;
|
795
|
+
case State::STOPPED:
|
796
|
+
// We can go from RUNNING or STARTED to STOPPED
|
797
|
+
assert(state == State::RUNNING || state == State::STARTED);
|
798
|
+
markers->record_interval(Marker::Type::MARKER_THREAD_RUNNING, from, now);
|
799
|
+
stopped_at = now;
|
800
|
+
capture_name();
|
801
|
+
|
802
|
+
break;
|
803
|
+
}
|
804
|
+
|
805
|
+
state = new_state;
|
806
|
+
state_changed_at = now;
|
807
|
+
}
|
808
|
+
|
809
|
+
bool running() {
|
810
|
+
return state != State::STOPPED;
|
811
|
+
}
|
812
|
+
|
813
|
+
void capture_name() {
|
814
|
+
char buf[128];
|
815
|
+
int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
816
|
+
if (rc == 0)
|
817
|
+
name = std::string(buf);
|
818
|
+
}
|
819
|
+
};
|
820
|
+
|
821
|
+
class ThreadTable {
|
822
|
+
public:
|
823
|
+
FrameList &frame_list;
|
824
|
+
|
825
|
+
std::vector<Thread> list;
|
826
|
+
std::mutex mutex;
|
827
|
+
|
828
|
+
ThreadTable(FrameList &frame_list) : frame_list(frame_list) {
|
829
|
+
}
|
830
|
+
|
831
|
+
void started(MarkerTable *markers) {
|
832
|
+
//const std::lock_guard<std::mutex> lock(mutex);
|
833
|
+
|
834
|
+
//list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
|
835
|
+
markers->record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
836
|
+
set_state(Thread::State::STARTED, markers);
|
837
|
+
}
|
838
|
+
|
839
|
+
void ready(MarkerTable *markers) {
|
840
|
+
set_state(Thread::State::READY, markers);
|
841
|
+
}
|
842
|
+
|
843
|
+
void resumed(MarkerTable *markers) {
|
844
|
+
set_state(Thread::State::RUNNING, markers);
|
845
|
+
}
|
846
|
+
|
847
|
+
void suspended(MarkerTable *markers) {
|
848
|
+
set_state(Thread::State::SUSPENDED, markers);
|
849
|
+
}
|
850
|
+
|
851
|
+
void stopped(MarkerTable *markers) {
|
852
|
+
markers->record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
853
|
+
set_state(Thread::State::STOPPED, markers);
|
854
|
+
}
|
855
|
+
|
856
|
+
private:
|
857
|
+
void set_state(Thread::State new_state, MarkerTable *markers) {
|
858
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
859
|
+
|
860
|
+
pthread_t current_thread = pthread_self();
|
861
|
+
//cerr << "set state=" << new_state << " thread=" << gettid() << endl;
|
862
|
+
|
863
|
+
for (auto &thread : list) {
|
864
|
+
if (pthread_equal(current_thread, thread.pthread_id)) {
|
865
|
+
if (new_state == Thread::State::SUSPENDED) {
|
866
|
+
|
867
|
+
RawSample sample;
|
868
|
+
sample.sample();
|
869
|
+
|
870
|
+
thread.stack_on_suspend_idx = thread.translator.translate(frame_list, sample);
|
871
|
+
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
872
|
+
}
|
873
|
+
|
874
|
+
thread.set_state(new_state, markers);
|
875
|
+
|
876
|
+
return;
|
877
|
+
}
|
878
|
+
}
|
879
|
+
|
880
|
+
pid_t native_tid = get_native_thread_id();
|
881
|
+
list.emplace_back(new_state);
|
882
|
+
}
|
883
|
+
};
|
884
|
+
|
451
885
|
class BaseCollector {
|
452
886
|
protected:
|
453
887
|
|
@@ -459,13 +893,19 @@ class BaseCollector {
|
|
459
893
|
bool running = false;
|
460
894
|
FrameList frame_list;
|
461
895
|
|
896
|
+
TimeStamp started_at;
|
897
|
+
|
898
|
+
virtual ~BaseCollector() {}
|
899
|
+
|
462
900
|
virtual bool start() {
|
463
901
|
if (running) {
|
464
902
|
return false;
|
465
|
-
} else {
|
466
|
-
running = true;
|
467
|
-
return true;
|
468
903
|
}
|
904
|
+
|
905
|
+
started_at = TimeStamp::Now();
|
906
|
+
|
907
|
+
running = true;
|
908
|
+
return true;
|
469
909
|
}
|
470
910
|
|
471
911
|
virtual VALUE stop() {
|
@@ -477,6 +917,21 @@ class BaseCollector {
|
|
477
917
|
return Qnil;
|
478
918
|
}
|
479
919
|
|
920
|
+
void write_meta(VALUE result) {
|
921
|
+
VALUE meta = rb_hash_new();
|
922
|
+
rb_ivar_set(result, rb_intern("@meta"), meta);
|
923
|
+
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
924
|
+
|
925
|
+
}
|
926
|
+
|
927
|
+
virtual VALUE build_collector_result() {
|
928
|
+
VALUE result = rb_obj_alloc(rb_cVernierResult);
|
929
|
+
|
930
|
+
write_meta(result);
|
931
|
+
|
932
|
+
return result;
|
933
|
+
}
|
934
|
+
|
480
935
|
virtual void sample() {
|
481
936
|
rb_raise(rb_eRuntimeError, "collector doesn't support manual sampling");
|
482
937
|
};
|
@@ -491,14 +946,15 @@ class BaseCollector {
|
|
491
946
|
};
|
492
947
|
|
493
948
|
class CustomCollector : public BaseCollector {
|
494
|
-
|
949
|
+
SampleList samples;
|
495
950
|
|
496
951
|
void sample() {
|
497
952
|
RawSample sample;
|
498
953
|
sample.sample();
|
499
954
|
int stack_index = frame_list.stack_index(sample);
|
500
955
|
|
501
|
-
|
956
|
+
native_thread_id_t thread_id = 0;
|
957
|
+
samples.record_sample(stack_index, TimeStamp::Now(), thread_id, CATEGORY_NORMAL);
|
502
958
|
}
|
503
959
|
|
504
960
|
VALUE stop() {
|
@@ -514,17 +970,16 @@ class CustomCollector : public BaseCollector {
|
|
514
970
|
}
|
515
971
|
|
516
972
|
VALUE build_collector_result() {
|
517
|
-
VALUE result =
|
973
|
+
VALUE result = BaseCollector::build_collector_result();
|
518
974
|
|
519
|
-
VALUE
|
520
|
-
rb_ivar_set(result, rb_intern("@
|
521
|
-
VALUE weights = rb_ary_new();
|
522
|
-
rb_ivar_set(result, rb_intern("@weights"), weights);
|
975
|
+
VALUE threads = rb_hash_new();
|
976
|
+
rb_ivar_set(result, rb_intern("@threads"), threads);
|
523
977
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
978
|
+
VALUE thread_hash = rb_hash_new();
|
979
|
+
samples.write_result(thread_hash);
|
980
|
+
|
981
|
+
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
982
|
+
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
528
983
|
|
529
984
|
frame_list.write_result(result);
|
530
985
|
|
@@ -557,16 +1012,18 @@ class RetainedCollector : public BaseCollector {
|
|
557
1012
|
|
558
1013
|
static void newobj_i(VALUE tpval, void *data) {
|
559
1014
|
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
560
|
-
|
1015
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
1016
|
+
VALUE obj = rb_tracearg_object(tparg);
|
561
1017
|
|
562
|
-
collector->record(
|
1018
|
+
collector->record(obj);
|
563
1019
|
}
|
564
1020
|
|
565
1021
|
static void freeobj_i(VALUE tpval, void *data) {
|
566
1022
|
RetainedCollector *collector = static_cast<RetainedCollector *>(data);
|
567
|
-
|
1023
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
1024
|
+
VALUE obj = rb_tracearg_object(tparg);
|
568
1025
|
|
569
|
-
collector->object_frames.erase(
|
1026
|
+
collector->object_frames.erase(obj);
|
570
1027
|
}
|
571
1028
|
|
572
1029
|
public:
|
@@ -621,12 +1078,18 @@ class RetainedCollector : public BaseCollector {
|
|
621
1078
|
RetainedCollector *collector = this;
|
622
1079
|
FrameList &frame_list = collector->frame_list;
|
623
1080
|
|
624
|
-
VALUE result =
|
1081
|
+
VALUE result = BaseCollector::build_collector_result();
|
625
1082
|
|
1083
|
+
VALUE threads = rb_hash_new();
|
1084
|
+
rb_ivar_set(result, rb_intern("@threads"), threads);
|
1085
|
+
VALUE thread_hash = rb_hash_new();
|
1086
|
+
rb_hash_aset(threads, ULL2NUM(0), thread_hash);
|
1087
|
+
|
1088
|
+
rb_hash_aset(thread_hash, sym("tid"), ULL2NUM(0));
|
626
1089
|
VALUE samples = rb_ary_new();
|
627
|
-
|
1090
|
+
rb_hash_aset(thread_hash, sym("samples"), samples);
|
628
1091
|
VALUE weights = rb_ary_new();
|
629
|
-
|
1092
|
+
rb_hash_aset(thread_hash, sym("weights"), weights);
|
630
1093
|
|
631
1094
|
for (auto& obj: collector->object_list) {
|
632
1095
|
const auto search = collector->object_frames.find(obj);
|
@@ -655,160 +1118,68 @@ class RetainedCollector : public BaseCollector {
|
|
655
1118
|
}
|
656
1119
|
};
|
657
1120
|
|
658
|
-
|
1121
|
+
class GlobalSignalHandler {
|
1122
|
+
static LiveSample *live_sample;
|
659
1123
|
|
660
|
-
class Thread {
|
661
1124
|
public:
|
662
|
-
static
|
663
|
-
|
664
|
-
|
665
|
-
int e = pthread_threadid_np(pthread_self(), &thread_id);
|
666
|
-
if (e != 0) rb_syserr_fail(e, "pthread_threadid_np");
|
667
|
-
return thread_id;
|
668
|
-
#else
|
669
|
-
return gettid();
|
670
|
-
#endif
|
1125
|
+
static GlobalSignalHandler *get_instance() {
|
1126
|
+
static GlobalSignalHandler instance;
|
1127
|
+
return &instance;
|
671
1128
|
}
|
672
1129
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
SUSPENDED,
|
677
|
-
STOPPED
|
678
|
-
};
|
679
|
-
|
680
|
-
pthread_t pthread_id;
|
681
|
-
native_thread_id_t native_tid;
|
682
|
-
State state;
|
683
|
-
|
684
|
-
TimeStamp state_changed_at;
|
685
|
-
TimeStamp started_at;
|
686
|
-
TimeStamp stopped_at;
|
687
|
-
|
688
|
-
RawSample stack_on_suspend;
|
689
|
-
|
690
|
-
std::string name;
|
691
|
-
|
692
|
-
Thread(State state) : state(state) {
|
693
|
-
pthread_id = pthread_self();
|
694
|
-
native_tid = get_native_thread_id();
|
695
|
-
started_at = state_changed_at = TimeStamp::Now();
|
696
|
-
}
|
697
|
-
|
698
|
-
void set_state(State new_state) {
|
699
|
-
if (state == Thread::State::STOPPED) {
|
700
|
-
return;
|
701
|
-
}
|
702
|
-
|
703
|
-
auto now = TimeStamp::Now();
|
704
|
-
|
705
|
-
state = new_state;
|
706
|
-
state_changed_at = now;
|
707
|
-
if (new_state == State::STARTED) {
|
708
|
-
if (started_at.zero()) {
|
709
|
-
started_at = now;
|
710
|
-
}
|
711
|
-
} else if (new_state == State::STOPPED) {
|
712
|
-
stopped_at = now;
|
1130
|
+
void install() {
|
1131
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
1132
|
+
count++;
|
713
1133
|
|
714
|
-
|
715
|
-
}
|
1134
|
+
if (count == 1) setup_signal_handler();
|
716
1135
|
}
|
717
1136
|
|
718
|
-
|
719
|
-
|
720
|
-
|
1137
|
+
void uninstall() {
|
1138
|
+
const std::lock_guard<std::mutex> lock(mutex);
|
1139
|
+
count--;
|
721
1140
|
|
722
|
-
|
723
|
-
char buf[128];
|
724
|
-
int rc = pthread_getname_np(pthread_id, buf, sizeof(buf));
|
725
|
-
if (rc == 0)
|
726
|
-
name = std::string(buf);
|
1141
|
+
if (count == 0) clear_signal_handler();
|
727
1142
|
}
|
728
|
-
};
|
729
|
-
|
730
|
-
class Marker {
|
731
|
-
public:
|
732
|
-
enum Type {
|
733
|
-
MARKER_GVL_THREAD_STARTED,
|
734
|
-
MARKER_GVL_THREAD_READY,
|
735
|
-
MARKER_GVL_THREAD_RESUMED,
|
736
|
-
MARKER_GVL_THREAD_SUSPENDED,
|
737
|
-
MARKER_GVL_THREAD_EXITED,
|
738
|
-
|
739
|
-
MARKER_GC_START,
|
740
|
-
MARKER_GC_END_MARK,
|
741
|
-
MARKER_GC_END_SWEEP,
|
742
|
-
MARKER_GC_ENTER,
|
743
|
-
MARKER_GC_EXIT,
|
744
1143
|
|
745
|
-
|
746
|
-
};
|
747
|
-
Type type;
|
748
|
-
TimeStamp timestamp;
|
749
|
-
native_thread_id_t thread_id;
|
750
|
-
};
|
751
|
-
|
752
|
-
class MarkerTable {
|
753
|
-
public:
|
754
|
-
std::vector<Marker> list;
|
755
|
-
std::mutex mutex;
|
756
|
-
|
757
|
-
void record(Marker::Type type) {
|
1144
|
+
void record_sample(LiveSample &sample, pthread_t pthread_id) {
|
758
1145
|
const std::lock_guard<std::mutex> lock(mutex);
|
759
1146
|
|
760
|
-
|
1147
|
+
live_sample = &sample;
|
1148
|
+
if (pthread_kill(pthread_id, SIGPROF)) {
|
1149
|
+
rb_bug("pthread_kill failed");
|
1150
|
+
}
|
1151
|
+
sample.wait();
|
1152
|
+
live_sample = NULL;
|
761
1153
|
}
|
762
|
-
};
|
763
|
-
|
764
|
-
extern "C" int ruby_thread_has_gvl_p(void);
|
765
1154
|
|
766
|
-
|
767
|
-
public:
|
768
|
-
std::vector<Thread> list;
|
1155
|
+
private:
|
769
1156
|
std::mutex mutex;
|
1157
|
+
int count;
|
770
1158
|
|
771
|
-
void
|
772
|
-
|
773
|
-
|
774
|
-
//list.push_back(Thread{pthread_self(), Thread::State::SUSPENDED});
|
775
|
-
set_state(Thread::State::STARTED);
|
1159
|
+
static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
|
1160
|
+
assert(live_sample);
|
1161
|
+
live_sample->sample_current_thread();
|
776
1162
|
}
|
777
1163
|
|
778
|
-
void
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
for (auto &thread : list) {
|
785
|
-
if (pthread_equal(current_thread, thread.pthread_id)) {
|
786
|
-
thread.set_state(new_state);
|
787
|
-
|
788
|
-
if (new_state == Thread::State::SUSPENDED) {
|
789
|
-
thread.stack_on_suspend.sample();
|
790
|
-
//cerr << gettid() << " suspended! Stack size:" << thread.stack_on_suspend.size() << endl;
|
791
|
-
}
|
792
|
-
return;
|
793
|
-
}
|
794
|
-
}
|
795
|
-
|
796
|
-
pid_t native_tid = Thread::get_native_thread_id();
|
797
|
-
list.emplace_back(new_state);
|
1164
|
+
void setup_signal_handler() {
|
1165
|
+
struct sigaction sa;
|
1166
|
+
sa.sa_sigaction = signal_handler;
|
1167
|
+
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
1168
|
+
sigemptyset(&sa.sa_mask);
|
1169
|
+
sigaction(SIGPROF, &sa, NULL);
|
798
1170
|
}
|
799
|
-
};
|
800
1171
|
|
801
|
-
|
802
|
-
|
803
|
-
|
1172
|
+
void clear_signal_handler() {
|
1173
|
+
struct sigaction sa;
|
1174
|
+
sa.sa_handler = SIG_IGN;
|
1175
|
+
sa.sa_flags = SA_RESTART;
|
1176
|
+
sigemptyset(&sa.sa_mask);
|
1177
|
+
sigaction(SIGPROF, &sa, NULL);
|
1178
|
+
}
|
804
1179
|
};
|
1180
|
+
LiveSample *GlobalSignalHandler::live_sample;
|
805
1181
|
|
806
1182
|
class TimeCollector : public BaseCollector {
|
807
|
-
std::vector<int> samples;
|
808
|
-
std::vector<TimeStamp> timestamps;
|
809
|
-
std::vector<native_thread_id_t> sample_threads;
|
810
|
-
std::vector<Category> sample_categories;
|
811
|
-
|
812
1183
|
MarkerTable markers;
|
813
1184
|
ThreadTable threads;
|
814
1185
|
|
@@ -817,41 +1188,31 @@ class TimeCollector : public BaseCollector {
|
|
817
1188
|
atomic_bool running;
|
818
1189
|
SamplerSemaphore thread_stopped;
|
819
1190
|
|
820
|
-
static inline LiveSample *live_sample;
|
821
|
-
|
822
|
-
TimeStamp started_at;
|
823
1191
|
TimeStamp interval;
|
824
1192
|
|
825
1193
|
public:
|
826
|
-
TimeCollector(TimeStamp interval) : interval(interval) {
|
1194
|
+
TimeCollector(TimeStamp interval) : interval(interval), threads(frame_list) {
|
827
1195
|
}
|
828
1196
|
|
829
1197
|
private:
|
830
1198
|
|
831
|
-
void record_sample(const RawSample &sample, TimeStamp time,
|
1199
|
+
void record_sample(const RawSample &sample, TimeStamp time, Thread &thread, Category category) {
|
832
1200
|
if (!sample.empty()) {
|
833
|
-
int stack_index =
|
834
|
-
samples.
|
835
|
-
|
836
|
-
|
837
|
-
|
1201
|
+
int stack_index = thread.translator.translate(frame_list, sample);
|
1202
|
+
thread.samples.record_sample(
|
1203
|
+
stack_index,
|
1204
|
+
time,
|
1205
|
+
thread.native_tid,
|
1206
|
+
category
|
1207
|
+
);
|
838
1208
|
}
|
839
1209
|
}
|
840
1210
|
|
841
|
-
static void signal_handler(int sig, siginfo_t* sinfo, void* ucontext) {
|
842
|
-
assert(live_sample);
|
843
|
-
live_sample->sample_current_thread();
|
844
|
-
}
|
845
|
-
|
846
1211
|
VALUE get_markers() {
|
847
|
-
VALUE list =
|
1212
|
+
VALUE list = rb_ary_new2(this->markers.list.size());
|
848
1213
|
|
849
1214
|
for (auto& marker: this->markers.list) {
|
850
|
-
|
851
|
-
record[0] = ULL2NUM(marker.thread_id);
|
852
|
-
record[1] = INT2NUM(marker.type);
|
853
|
-
record[2] = ULL2NUM(marker.timestamp.nanoseconds());
|
854
|
-
rb_ary_push(list, rb_ary_new_from_values(3, record));
|
1215
|
+
rb_ary_push(list, marker.to_array());
|
855
1216
|
}
|
856
1217
|
|
857
1218
|
return list;
|
@@ -859,20 +1220,16 @@ class TimeCollector : public BaseCollector {
|
|
859
1220
|
|
860
1221
|
void sample_thread_run() {
|
861
1222
|
LiveSample sample;
|
862
|
-
live_sample = &sample;
|
863
1223
|
|
864
1224
|
TimeStamp next_sample_schedule = TimeStamp::Now();
|
865
1225
|
while (running) {
|
866
1226
|
TimeStamp sample_start = TimeStamp::Now();
|
867
1227
|
|
868
1228
|
threads.mutex.lock();
|
869
|
-
for (auto thread : threads.list) {
|
1229
|
+
for (auto &thread : threads.list) {
|
870
1230
|
//if (thread.state == Thread::State::RUNNING) {
|
871
|
-
if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.
|
872
|
-
|
873
|
-
rb_bug("pthread_kill failed");
|
874
|
-
}
|
875
|
-
sample.wait();
|
1231
|
+
if (thread.state == Thread::State::RUNNING || (thread.state == Thread::State::SUSPENDED && thread.stack_on_suspend_idx < 0)) {
|
1232
|
+
GlobalSignalHandler::get_instance()->record_sample(sample, thread.pthread_id);
|
876
1233
|
|
877
1234
|
if (sample.sample.gc) {
|
878
1235
|
// fprintf(stderr, "skipping GC sample\n");
|
@@ -880,10 +1237,15 @@ class TimeCollector : public BaseCollector {
|
|
880
1237
|
record_sample(sample.sample, sample_start, thread, CATEGORY_NORMAL);
|
881
1238
|
}
|
882
1239
|
} else if (thread.state == Thread::State::SUSPENDED) {
|
883
|
-
record_sample(
|
1240
|
+
thread.samples.record_sample(
|
1241
|
+
thread.stack_on_suspend_idx,
|
1242
|
+
sample_start,
|
1243
|
+
thread.native_tid,
|
1244
|
+
CATEGORY_IDLE);
|
884
1245
|
} else {
|
885
1246
|
}
|
886
1247
|
}
|
1248
|
+
|
887
1249
|
threads.mutex.unlock();
|
888
1250
|
|
889
1251
|
TimeStamp sample_complete = TimeStamp::Now();
|
@@ -899,8 +1261,6 @@ class TimeCollector : public BaseCollector {
|
|
899
1261
|
TimeStamp::Sleep(sleep_time);
|
900
1262
|
}
|
901
1263
|
|
902
|
-
live_sample = NULL;
|
903
|
-
|
904
1264
|
thread_stopped.post();
|
905
1265
|
}
|
906
1266
|
|
@@ -910,10 +1270,21 @@ class TimeCollector : public BaseCollector {
|
|
910
1270
|
return NULL;
|
911
1271
|
}
|
912
1272
|
|
913
|
-
static void
|
914
|
-
TimeCollector *collector = static_cast<TimeCollector *>(data);
|
915
|
-
|
916
|
-
|
1273
|
+
static void internal_thread_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
|
1274
|
+
TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
|
1275
|
+
|
1276
|
+
switch (event) {
|
1277
|
+
case RUBY_EVENT_THREAD_BEGIN:
|
1278
|
+
collector->threads.started(&collector->markers);
|
1279
|
+
break;
|
1280
|
+
case RUBY_EVENT_THREAD_END:
|
1281
|
+
collector->threads.stopped(&collector->markers);
|
1282
|
+
break;
|
1283
|
+
}
|
1284
|
+
}
|
1285
|
+
|
1286
|
+
static void internal_gc_event_cb(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass) {
|
1287
|
+
TimeCollector *collector = static_cast<TimeCollector *>((void *)NUM2ULL(data));
|
917
1288
|
|
918
1289
|
switch (event) {
|
919
1290
|
case RUBY_INTERNAL_EVENT_GC_START:
|
@@ -926,10 +1297,10 @@ class TimeCollector : public BaseCollector {
|
|
926
1297
|
collector->markers.record(Marker::Type::MARKER_GC_END_SWEEP);
|
927
1298
|
break;
|
928
1299
|
case RUBY_INTERNAL_EVENT_GC_ENTER:
|
929
|
-
collector->markers.
|
1300
|
+
collector->markers.record_gc_entered();
|
930
1301
|
break;
|
931
1302
|
case RUBY_INTERNAL_EVENT_GC_EXIT:
|
932
|
-
collector->markers.
|
1303
|
+
collector->markers.record_gc_leave();
|
933
1304
|
break;
|
934
1305
|
}
|
935
1306
|
}
|
@@ -939,44 +1310,27 @@ class TimeCollector : public BaseCollector {
|
|
939
1310
|
//cerr << "internal thread event" << event << " at " << TimeStamp::Now() << endl;
|
940
1311
|
|
941
1312
|
switch (event) {
|
942
|
-
case RUBY_INTERNAL_THREAD_EVENT_STARTED:
|
943
|
-
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_STARTED);
|
944
|
-
collector->threads.started();
|
945
|
-
break;
|
946
1313
|
case RUBY_INTERNAL_THREAD_EVENT_READY:
|
947
|
-
collector->
|
1314
|
+
collector->threads.ready(&collector->markers);
|
948
1315
|
break;
|
949
1316
|
case RUBY_INTERNAL_THREAD_EVENT_RESUMED:
|
950
|
-
collector->
|
951
|
-
collector->threads.set_state(Thread::State::RUNNING);
|
1317
|
+
collector->threads.resumed(&collector->markers);
|
952
1318
|
break;
|
953
1319
|
case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED:
|
954
|
-
collector->
|
955
|
-
collector->threads.set_state(Thread::State::SUSPENDED);
|
956
|
-
break;
|
957
|
-
case RUBY_INTERNAL_THREAD_EVENT_EXITED:
|
958
|
-
collector->markers.record(Marker::Type::MARKER_GVL_THREAD_EXITED);
|
959
|
-
collector->threads.set_state(Thread::State::STOPPED);
|
1320
|
+
collector->threads.suspended(&collector->markers);
|
960
1321
|
break;
|
961
1322
|
|
962
1323
|
}
|
963
1324
|
}
|
964
1325
|
|
965
1326
|
rb_internal_thread_event_hook_t *thread_hook;
|
966
|
-
VALUE gc_hook;
|
967
1327
|
|
968
1328
|
bool start() {
|
969
1329
|
if (!BaseCollector::start()) {
|
970
1330
|
return false;
|
971
1331
|
}
|
972
1332
|
|
973
|
-
|
974
|
-
|
975
|
-
struct sigaction sa;
|
976
|
-
sa.sa_sigaction = signal_handler;
|
977
|
-
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
978
|
-
sigemptyset(&sa.sa_mask);
|
979
|
-
sigaction(SIGPROF, &sa, NULL);
|
1333
|
+
GlobalSignalHandler::get_instance()->install();
|
980
1334
|
|
981
1335
|
running = true;
|
982
1336
|
|
@@ -986,15 +1340,16 @@ class TimeCollector : public BaseCollector {
|
|
986
1340
|
rb_bug("pthread_create");
|
987
1341
|
}
|
988
1342
|
|
989
|
-
// Set the state of the current Ruby thread to RUNNING
|
990
|
-
//
|
991
|
-
//
|
992
|
-
//
|
993
|
-
|
1343
|
+
// Set the state of the current Ruby thread to RUNNING, which we know it
|
1344
|
+
// is as it must have held the GVL to start the collector. We want to
|
1345
|
+
// have at least one thread in our thread list because it's possible
|
1346
|
+
// that the profile might be such that we don't get any thread switch
|
1347
|
+
// events and we need at least one
|
1348
|
+
this->threads.resumed(&this->markers);
|
994
1349
|
|
995
1350
|
thread_hook = rb_internal_thread_add_event_hook(internal_thread_event_cb, RUBY_INTERNAL_THREAD_EVENT_MASK, this);
|
996
|
-
|
997
|
-
|
1351
|
+
rb_add_event_hook(internal_gc_event_cb, RUBY_INTERNAL_EVENTS, PTR2NUM((void *)this));
|
1352
|
+
rb_add_event_hook(internal_thread_event_cb, RUBY_NORMAL_EVENTS, PTR2NUM((void *)this));
|
998
1353
|
|
999
1354
|
return true;
|
1000
1355
|
}
|
@@ -1005,14 +1360,11 @@ class TimeCollector : public BaseCollector {
|
|
1005
1360
|
running = false;
|
1006
1361
|
thread_stopped.wait();
|
1007
1362
|
|
1008
|
-
|
1009
|
-
sa.sa_handler = SIG_IGN;
|
1010
|
-
sa.sa_flags = SA_RESTART;
|
1011
|
-
sigemptyset(&sa.sa_mask);
|
1012
|
-
sigaction(SIGPROF, &sa, NULL);
|
1363
|
+
GlobalSignalHandler::get_instance()->uninstall();
|
1013
1364
|
|
1014
1365
|
rb_internal_thread_remove_event_hook(thread_hook);
|
1015
|
-
|
1366
|
+
rb_remove_event_hook(internal_gc_event_cb);
|
1367
|
+
rb_remove_event_hook(internal_thread_event_cb);
|
1016
1368
|
|
1017
1369
|
// capture thread names
|
1018
1370
|
for (auto& thread: this->threads.list) {
|
@@ -1031,45 +1383,15 @@ class TimeCollector : public BaseCollector {
|
|
1031
1383
|
}
|
1032
1384
|
|
1033
1385
|
VALUE build_collector_result() {
|
1034
|
-
VALUE result =
|
1035
|
-
|
1036
|
-
VALUE meta = rb_hash_new();
|
1037
|
-
rb_ivar_set(result, rb_intern("@meta"), meta);
|
1038
|
-
rb_hash_aset(meta, sym("started_at"), ULL2NUM(started_at.nanoseconds()));
|
1039
|
-
|
1040
|
-
VALUE samples = rb_ary_new();
|
1041
|
-
rb_ivar_set(result, rb_intern("@samples"), samples);
|
1042
|
-
VALUE weights = rb_ary_new();
|
1043
|
-
rb_ivar_set(result, rb_intern("@weights"), weights);
|
1044
|
-
for (auto& stack_index: this->samples) {
|
1045
|
-
rb_ary_push(samples, INT2NUM(stack_index));
|
1046
|
-
rb_ary_push(weights, INT2NUM(1));
|
1047
|
-
}
|
1048
|
-
|
1049
|
-
VALUE timestamps = rb_ary_new();
|
1050
|
-
rb_ivar_set(result, rb_intern("@timestamps"), timestamps);
|
1051
|
-
|
1052
|
-
for (auto& timestamp: this->timestamps) {
|
1053
|
-
rb_ary_push(timestamps, ULL2NUM(timestamp.nanoseconds()));
|
1054
|
-
}
|
1055
|
-
|
1056
|
-
VALUE sample_threads = rb_ary_new();
|
1057
|
-
rb_ivar_set(result, rb_intern("@sample_threads"), sample_threads);
|
1058
|
-
for (auto& thread: this->sample_threads) {
|
1059
|
-
rb_ary_push(sample_threads, ULL2NUM(thread));
|
1060
|
-
}
|
1061
|
-
|
1062
|
-
VALUE sample_categories = rb_ary_new();
|
1063
|
-
rb_ivar_set(result, rb_intern("@sample_categories"), sample_categories);
|
1064
|
-
for (auto& cat: this->sample_categories) {
|
1065
|
-
rb_ary_push(sample_categories, INT2NUM(cat));
|
1066
|
-
}
|
1386
|
+
VALUE result = BaseCollector::build_collector_result();
|
1067
1387
|
|
1068
1388
|
VALUE threads = rb_hash_new();
|
1069
1389
|
rb_ivar_set(result, rb_intern("@threads"), threads);
|
1070
1390
|
|
1071
1391
|
for (const auto& thread: this->threads.list) {
|
1072
1392
|
VALUE hash = rb_hash_new();
|
1393
|
+
thread.samples.write_result(hash);
|
1394
|
+
|
1073
1395
|
rb_hash_aset(threads, ULL2NUM(thread.native_tid), hash);
|
1074
1396
|
rb_hash_aset(hash, sym("tid"), ULL2NUM(thread.native_tid));
|
1075
1397
|
rb_hash_aset(hash, sym("started_at"), ULL2NUM(thread.started_at.nanoseconds()));
|
@@ -1087,7 +1409,6 @@ class TimeCollector : public BaseCollector {
|
|
1087
1409
|
|
1088
1410
|
void mark() {
|
1089
1411
|
frame_list.mark_frames();
|
1090
|
-
rb_gc_mark(gc_hook);
|
1091
1412
|
|
1092
1413
|
//for (int i = 0; i < queued_length; i++) {
|
1093
1414
|
// rb_gc_mark(queued_frames[i]);
|
@@ -1182,14 +1503,11 @@ static VALUE collector_new(VALUE self, VALUE mode, VALUE options) {
|
|
1182
1503
|
}
|
1183
1504
|
|
1184
1505
|
static void
|
1185
|
-
Init_consts() {
|
1506
|
+
Init_consts(VALUE rb_mVernierMarkerPhase) {
|
1186
1507
|
#define MARKER_CONST(name) \
|
1187
1508
|
rb_define_const(rb_mVernierMarkerType, #name, INT2NUM(Marker::Type::MARKER_##name))
|
1188
1509
|
|
1189
1510
|
MARKER_CONST(GVL_THREAD_STARTED);
|
1190
|
-
MARKER_CONST(GVL_THREAD_READY);
|
1191
|
-
MARKER_CONST(GVL_THREAD_RESUMED);
|
1192
|
-
MARKER_CONST(GVL_THREAD_SUSPENDED);
|
1193
1511
|
MARKER_CONST(GVL_THREAD_EXITED);
|
1194
1512
|
|
1195
1513
|
MARKER_CONST(GC_START);
|
@@ -1197,8 +1515,22 @@ Init_consts() {
|
|
1197
1515
|
MARKER_CONST(GC_END_SWEEP);
|
1198
1516
|
MARKER_CONST(GC_ENTER);
|
1199
1517
|
MARKER_CONST(GC_EXIT);
|
1518
|
+
MARKER_CONST(GC_PAUSE);
|
1519
|
+
|
1520
|
+
MARKER_CONST(THREAD_RUNNING);
|
1521
|
+
MARKER_CONST(THREAD_STALLED);
|
1522
|
+
MARKER_CONST(THREAD_SUSPENDED);
|
1200
1523
|
|
1201
1524
|
#undef MARKER_CONST
|
1525
|
+
|
1526
|
+
#define PHASE_CONST(name) \
|
1527
|
+
rb_define_const(rb_mVernierMarkerPhase, #name, INT2NUM(Marker::Phase::name))
|
1528
|
+
|
1529
|
+
PHASE_CONST(INSTANT);
|
1530
|
+
PHASE_CONST(INTERVAL);
|
1531
|
+
PHASE_CONST(INTERVAL_START);
|
1532
|
+
PHASE_CONST(INTERVAL_END);
|
1533
|
+
#undef PHASE_CONST
|
1202
1534
|
}
|
1203
1535
|
|
1204
1536
|
extern "C" void
|
@@ -1207,6 +1539,7 @@ Init_vernier(void)
|
|
1207
1539
|
rb_mVernier = rb_define_module("Vernier");
|
1208
1540
|
rb_cVernierResult = rb_define_class_under(rb_mVernier, "Result", rb_cObject);
|
1209
1541
|
VALUE rb_mVernierMarker = rb_define_module_under(rb_mVernier, "Marker");
|
1542
|
+
VALUE rb_mVernierMarkerPhase = rb_define_module_under(rb_mVernierMarker, "Phase");
|
1210
1543
|
rb_mVernierMarkerType = rb_define_module_under(rb_mVernierMarker, "Type");
|
1211
1544
|
|
1212
1545
|
rb_cVernierCollector = rb_define_class_under(rb_mVernier, "Collector", rb_cObject);
|
@@ -1217,7 +1550,7 @@ Init_vernier(void)
|
|
1217
1550
|
rb_define_private_method(rb_cVernierCollector, "finish", collector_stop, 0);
|
1218
1551
|
rb_define_private_method(rb_cVernierCollector, "markers", markers, 0);
|
1219
1552
|
|
1220
|
-
Init_consts();
|
1553
|
+
Init_consts(rb_mVernierMarkerPhase);
|
1221
1554
|
|
1222
1555
|
//static VALUE gc_hook = Data_Wrap_Struct(rb_cObject, collector_mark, NULL, &_collector);
|
1223
1556
|
//rb_global_variable(&gc_hook);
|