pf2 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/CHANGELOG.md +22 -2
- data/Cargo.lock +186 -17
- data/Cargo.toml +1 -1
- data/README.md +17 -6
- data/Rakefile +8 -0
- data/crates/backtrace-sys2/.gitignore +1 -0
- data/crates/backtrace-sys2/Cargo.toml +9 -0
- data/crates/backtrace-sys2/build.rs +48 -0
- data/crates/backtrace-sys2/src/lib.rs +5 -0
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
- data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
- data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
- data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
- data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
- data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
- data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
- data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
- data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
- data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
- data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
- data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
- data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
- data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
- data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
- data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
- data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
- data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
- data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
- data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
- data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
- data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
- data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
- data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
- data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
- data/ext/pf2/Cargo.toml +1 -0
- data/ext/pf2/src/backtrace.rs +126 -0
- data/ext/pf2/src/lib.rs +1 -0
- data/ext/pf2/src/profile.rs +16 -1
- data/ext/pf2/src/profile_serializer.rs +95 -21
- data/ext/pf2/src/ringbuffer.rs +7 -0
- data/ext/pf2/src/ruby_init.rs +18 -6
- data/ext/pf2/src/sample.rs +22 -1
- data/ext/pf2/src/signal_scheduler/configuration.rs +7 -0
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +34 -27
- data/ext/pf2/src/signal_scheduler.rs +95 -26
- data/ext/pf2/src/timer_thread_scheduler.rs +88 -12
- data/ext/pf2/src/util.rs +2 -2
- data/lib/pf2/reporter.rb +12 -5
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +3 -6
- metadata +96 -2
data/ext/pf2/src/profile.rs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
use std::collections::HashSet;
|
|
2
1
|
use std::time::Instant;
|
|
2
|
+
use std::{collections::HashSet, ptr::null_mut};
|
|
3
3
|
|
|
4
4
|
use rb_sys::*;
|
|
5
5
|
|
|
6
|
+
use backtrace_sys2::backtrace_create_state;
|
|
7
|
+
|
|
8
|
+
use super::backtrace::{Backtrace, BacktraceState};
|
|
6
9
|
use super::ringbuffer::Ringbuffer;
|
|
7
10
|
use super::sample::Sample;
|
|
8
11
|
|
|
@@ -15,15 +18,27 @@ pub struct Profile {
|
|
|
15
18
|
pub start_timestamp: Instant,
|
|
16
19
|
pub samples: Vec<Sample>,
|
|
17
20
|
pub temporary_sample_buffer: Ringbuffer,
|
|
21
|
+
pub backtrace_state: BacktraceState,
|
|
18
22
|
known_values: HashSet<VALUE>,
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
impl Profile {
|
|
22
26
|
pub fn new() -> Self {
|
|
27
|
+
let backtrace_state = unsafe {
|
|
28
|
+
let ptr = backtrace_create_state(
|
|
29
|
+
null_mut(),
|
|
30
|
+
1,
|
|
31
|
+
Some(Backtrace::backtrace_error_callback),
|
|
32
|
+
null_mut(),
|
|
33
|
+
);
|
|
34
|
+
BacktraceState::new(ptr)
|
|
35
|
+
};
|
|
36
|
+
|
|
23
37
|
Self {
|
|
24
38
|
start_timestamp: Instant::now(),
|
|
25
39
|
samples: vec![],
|
|
26
40
|
temporary_sample_buffer: Ringbuffer::new(DEFAULT_RINGBUFFER_CAPACITY),
|
|
41
|
+
backtrace_state,
|
|
27
42
|
known_values: HashSet::new(),
|
|
28
43
|
}
|
|
29
44
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
use std::
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::ffi::{c_char, CStr};
|
|
3
|
+
use std::hash::Hasher;
|
|
2
4
|
|
|
3
5
|
use rb_sys::*;
|
|
4
6
|
|
|
7
|
+
use crate::backtrace::Backtrace;
|
|
5
8
|
use crate::profile::Profile;
|
|
6
9
|
|
|
7
10
|
#[derive(Debug, Deserialize, Serialize)]
|
|
@@ -56,9 +59,17 @@ struct StackTreeNode {
|
|
|
56
59
|
|
|
57
60
|
#[derive(Debug, Deserialize, Serialize)]
|
|
58
61
|
struct FrameTableEntry {
|
|
62
|
+
id: FrameTableId,
|
|
63
|
+
entry_type: FrameTableEntryType,
|
|
59
64
|
full_label: String,
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
#[derive(Debug, Deserialize, Serialize)]
|
|
68
|
+
enum FrameTableEntryType {
|
|
69
|
+
Ruby,
|
|
70
|
+
Native,
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
// Represents leaf (末端)
|
|
63
74
|
#[derive(Debug, Deserialize, Serialize)]
|
|
64
75
|
struct ProfileSample {
|
|
@@ -77,6 +88,73 @@ impl ProfileSerializer {
|
|
|
77
88
|
unsafe {
|
|
78
89
|
// Process each sample
|
|
79
90
|
for sample in profile.samples.iter() {
|
|
91
|
+
let mut merged_stack: Vec<FrameTableEntry> = vec![];
|
|
92
|
+
|
|
93
|
+
// Process C-level stack
|
|
94
|
+
|
|
95
|
+
// A vec to keep the "programmer's" C stack trace.
|
|
96
|
+
// A single PC may be mapped to multiple inlined frames,
|
|
97
|
+
// so we keep the expanded stack frame in this Vec.
|
|
98
|
+
let mut c_stack: Vec<String> = vec![];
|
|
99
|
+
for i in 0..sample.c_backtrace_pcs[0] {
|
|
100
|
+
let pc = sample.c_backtrace_pcs[i + 1];
|
|
101
|
+
Backtrace::backtrace_syminfo(
|
|
102
|
+
&profile.backtrace_state,
|
|
103
|
+
pc,
|
|
104
|
+
|_pc: usize, symname: *const c_char, _symval: usize, _symsize: usize| {
|
|
105
|
+
if symname.is_null() {
|
|
106
|
+
c_stack.push("(no symbol information)".to_owned());
|
|
107
|
+
} else {
|
|
108
|
+
c_stack.push(CStr::from_ptr(symname).to_str().unwrap().to_owned());
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
Some(Backtrace::backtrace_error_callback),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Strip the C stack trace:
|
|
116
|
+
// - Remove Pf2-related frames which are always captured
|
|
117
|
+
// - Remove frames below rb_vm_exec
|
|
118
|
+
let mut reached_ruby = false;
|
|
119
|
+
c_stack.retain(|frame| {
|
|
120
|
+
if reached_ruby {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if frame.contains("pf2") {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if frame.contains("rb_vm_exec") || frame.contains("vm_call_cfunc_with_frame") {
|
|
127
|
+
reached_ruby = true;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
true
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
for frame in c_stack.iter() {
|
|
134
|
+
merged_stack.push(FrameTableEntry {
|
|
135
|
+
id: calculate_id_for_c_frame(frame),
|
|
136
|
+
entry_type: FrameTableEntryType::Native,
|
|
137
|
+
full_label: frame.to_string(),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Process Ruby-level stack
|
|
142
|
+
|
|
143
|
+
let ruby_stack_depth = sample.line_count;
|
|
144
|
+
for i in 0..ruby_stack_depth {
|
|
145
|
+
let frame: VALUE = sample.frames[i as usize];
|
|
146
|
+
merged_stack.push(FrameTableEntry {
|
|
147
|
+
id: frame,
|
|
148
|
+
entry_type: FrameTableEntryType::Ruby,
|
|
149
|
+
full_label: CStr::from_ptr(rb_string_value_cstr(
|
|
150
|
+
&mut rb_profile_frame_full_label(frame),
|
|
151
|
+
))
|
|
152
|
+
.to_str()
|
|
153
|
+
.unwrap()
|
|
154
|
+
.to_owned(),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
80
158
|
// Find the Thread profile for this sample
|
|
81
159
|
let thread_serializer = serializer
|
|
82
160
|
.threads
|
|
@@ -86,34 +164,18 @@ impl ProfileSerializer {
|
|
|
86
164
|
// Stack frames, shallow to deep
|
|
87
165
|
let mut stack_tree = &mut thread_serializer.stack_tree;
|
|
88
166
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Register frame metadata to frame table, if not registered yet
|
|
93
|
-
let frame_table_id: FrameTableId = frame;
|
|
94
|
-
thread_serializer
|
|
95
|
-
.frame_table
|
|
96
|
-
.entry(frame_table_id)
|
|
97
|
-
.or_insert(FrameTableEntry {
|
|
98
|
-
full_label: CStr::from_ptr(rb_string_value_cstr(
|
|
99
|
-
&mut rb_profile_frame_full_label(frame),
|
|
100
|
-
))
|
|
101
|
-
.to_str()
|
|
102
|
-
.unwrap()
|
|
103
|
-
.to_string(),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
stack_tree = stack_tree.children.entry(frame_table_id).or_insert({
|
|
167
|
+
while let Some(frame_table_entry) = merged_stack.pop() {
|
|
168
|
+
stack_tree = stack_tree.children.entry(frame_table_entry.id).or_insert({
|
|
107
169
|
let node = StackTreeNode {
|
|
108
170
|
children: HashMap::new(),
|
|
109
171
|
node_id: sequence,
|
|
110
|
-
frame_id:
|
|
172
|
+
frame_id: frame_table_entry.id,
|
|
111
173
|
};
|
|
112
174
|
sequence += 1;
|
|
113
175
|
node
|
|
114
176
|
});
|
|
115
177
|
|
|
116
|
-
if
|
|
178
|
+
if merged_stack.is_empty() {
|
|
117
179
|
// This is the leaf node, record a Sample
|
|
118
180
|
let elapsed_ns = (sample.timestamp - profile.start_timestamp).as_nanos();
|
|
119
181
|
thread_serializer.samples.push(ProfileSample {
|
|
@@ -121,6 +183,12 @@ impl ProfileSerializer {
|
|
|
121
183
|
stack_tree_id: stack_tree.node_id,
|
|
122
184
|
});
|
|
123
185
|
}
|
|
186
|
+
|
|
187
|
+
// Register frame metadata to frame table, if not registered yet
|
|
188
|
+
thread_serializer
|
|
189
|
+
.frame_table
|
|
190
|
+
.entry(frame_table_entry.id)
|
|
191
|
+
.or_insert(frame_table_entry);
|
|
124
192
|
}
|
|
125
193
|
}
|
|
126
194
|
}
|
|
@@ -128,3 +196,9 @@ impl ProfileSerializer {
|
|
|
128
196
|
serde_json::to_string(&serializer).unwrap()
|
|
129
197
|
}
|
|
130
198
|
}
|
|
199
|
+
|
|
200
|
+
fn calculate_id_for_c_frame<T: std::hash::Hash>(t: &T) -> FrameTableId {
|
|
201
|
+
let mut s = std::collections::hash_map::DefaultHasher::new();
|
|
202
|
+
t.hash(&mut s);
|
|
203
|
+
s.finish()
|
|
204
|
+
}
|
data/ext/pf2/src/ringbuffer.rs
CHANGED
|
@@ -71,6 +71,7 @@ mod tests {
|
|
|
71
71
|
line_count: 0,
|
|
72
72
|
frames: [0; 500],
|
|
73
73
|
linenos: [0; 500],
|
|
74
|
+
c_backtrace_pcs: [0; 1001],
|
|
74
75
|
};
|
|
75
76
|
let sample2 = Sample {
|
|
76
77
|
ruby_thread: 2,
|
|
@@ -78,6 +79,7 @@ mod tests {
|
|
|
78
79
|
line_count: 0,
|
|
79
80
|
frames: [0; 500],
|
|
80
81
|
linenos: [0; 500],
|
|
82
|
+
c_backtrace_pcs: [0; 1001],
|
|
81
83
|
};
|
|
82
84
|
|
|
83
85
|
ringbuffer.push(sample1).unwrap();
|
|
@@ -97,6 +99,7 @@ mod tests {
|
|
|
97
99
|
line_count: 0,
|
|
98
100
|
frames: [0; 500],
|
|
99
101
|
linenos: [0; 500],
|
|
102
|
+
c_backtrace_pcs: [0; 1001],
|
|
100
103
|
};
|
|
101
104
|
let sample2 = Sample {
|
|
102
105
|
ruby_thread: 2,
|
|
@@ -104,6 +107,7 @@ mod tests {
|
|
|
104
107
|
line_count: 0,
|
|
105
108
|
frames: [0; 500],
|
|
106
109
|
linenos: [0; 500],
|
|
110
|
+
c_backtrace_pcs: [0; 1001],
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
ringbuffer.push(sample1).unwrap();
|
|
@@ -119,6 +123,7 @@ mod tests {
|
|
|
119
123
|
line_count: 0,
|
|
120
124
|
frames: [0; 500],
|
|
121
125
|
linenos: [0; 500],
|
|
126
|
+
c_backtrace_pcs: [0; 1001],
|
|
122
127
|
};
|
|
123
128
|
let sample2 = Sample {
|
|
124
129
|
ruby_thread: 2,
|
|
@@ -126,6 +131,7 @@ mod tests {
|
|
|
126
131
|
line_count: 0,
|
|
127
132
|
frames: [0; 500],
|
|
128
133
|
linenos: [0; 500],
|
|
134
|
+
c_backtrace_pcs: [0; 1001],
|
|
129
135
|
};
|
|
130
136
|
let sample3 = Sample {
|
|
131
137
|
ruby_thread: 3,
|
|
@@ -133,6 +139,7 @@ mod tests {
|
|
|
133
139
|
line_count: 0,
|
|
134
140
|
frames: [0; 500],
|
|
135
141
|
linenos: [0; 500],
|
|
142
|
+
c_backtrace_pcs: [0; 1001],
|
|
136
143
|
};
|
|
137
144
|
|
|
138
145
|
ringbuffer.push(sample1).unwrap();
|
data/ext/pf2/src/ruby_init.rs
CHANGED
|
@@ -26,16 +26,22 @@ extern "C" fn Init_pf2() {
|
|
|
26
26
|
let rb_mPf2_SignalScheduler =
|
|
27
27
|
rb_define_class_under(rb_mPf2, cstr!("SignalScheduler"), rb_cObject);
|
|
28
28
|
rb_define_alloc_func(rb_mPf2_SignalScheduler, Some(SignalScheduler::rb_alloc));
|
|
29
|
+
rb_define_method(
|
|
30
|
+
rb_mPf2_SignalScheduler,
|
|
31
|
+
cstr!("initialize"),
|
|
32
|
+
Some(to_ruby_cfunc_with_args(SignalScheduler::rb_initialize)),
|
|
33
|
+
-1,
|
|
34
|
+
);
|
|
29
35
|
rb_define_method(
|
|
30
36
|
rb_mPf2_SignalScheduler,
|
|
31
37
|
cstr!("start"),
|
|
32
|
-
Some(
|
|
33
|
-
|
|
38
|
+
Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_start)),
|
|
39
|
+
0,
|
|
34
40
|
);
|
|
35
41
|
rb_define_method(
|
|
36
42
|
rb_mPf2_SignalScheduler,
|
|
37
43
|
cstr!("stop"),
|
|
38
|
-
Some(
|
|
44
|
+
Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_stop)),
|
|
39
45
|
0,
|
|
40
46
|
);
|
|
41
47
|
}
|
|
@@ -46,16 +52,22 @@ extern "C" fn Init_pf2() {
|
|
|
46
52
|
rb_mPf2_TimerThreadScheduler,
|
|
47
53
|
Some(TimerThreadScheduler::rb_alloc),
|
|
48
54
|
);
|
|
55
|
+
rb_define_method(
|
|
56
|
+
rb_mPf2_TimerThreadScheduler,
|
|
57
|
+
cstr!("initialize"),
|
|
58
|
+
Some(to_ruby_cfunc_with_args(TimerThreadScheduler::rb_initialize)),
|
|
59
|
+
-1,
|
|
60
|
+
);
|
|
49
61
|
rb_define_method(
|
|
50
62
|
rb_mPf2_TimerThreadScheduler,
|
|
51
63
|
cstr!("start"),
|
|
52
|
-
Some(
|
|
53
|
-
|
|
64
|
+
Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_start)),
|
|
65
|
+
0,
|
|
54
66
|
);
|
|
55
67
|
rb_define_method(
|
|
56
68
|
rb_mPf2_TimerThreadScheduler,
|
|
57
69
|
cstr!("stop"),
|
|
58
|
-
Some(
|
|
70
|
+
Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_stop)),
|
|
59
71
|
0,
|
|
60
72
|
);
|
|
61
73
|
}
|
data/ext/pf2/src/sample.rs
CHANGED
|
@@ -2,7 +2,10 @@ use std::time::Instant;
|
|
|
2
2
|
|
|
3
3
|
use rb_sys::*;
|
|
4
4
|
|
|
5
|
+
use crate::backtrace::{Backtrace, BacktraceState};
|
|
6
|
+
|
|
5
7
|
const MAX_STACK_DEPTH: usize = 500;
|
|
8
|
+
const MAX_C_STACK_DEPTH: usize = 1000;
|
|
6
9
|
|
|
7
10
|
#[derive(Debug, PartialEq)]
|
|
8
11
|
pub struct Sample {
|
|
@@ -11,18 +14,36 @@ pub struct Sample {
|
|
|
11
14
|
pub line_count: i32,
|
|
12
15
|
pub frames: [VALUE; MAX_STACK_DEPTH],
|
|
13
16
|
pub linenos: [i32; MAX_STACK_DEPTH],
|
|
17
|
+
pub c_backtrace_pcs: [usize; MAX_C_STACK_DEPTH + 1],
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
impl Sample {
|
|
17
21
|
// Nearly async-signal-safe
|
|
18
22
|
// (rb_profile_thread_frames isn't defined as a-s-s)
|
|
19
|
-
pub fn capture(ruby_thread: VALUE) -> Self {
|
|
23
|
+
pub fn capture(ruby_thread: VALUE, backtrace_state: &BacktraceState) -> Self {
|
|
24
|
+
let mut c_backtrace_pcs = [0; MAX_C_STACK_DEPTH + 1];
|
|
25
|
+
|
|
26
|
+
Backtrace::backtrace_simple(
|
|
27
|
+
backtrace_state,
|
|
28
|
+
0,
|
|
29
|
+
|pc: usize| -> i32 {
|
|
30
|
+
if c_backtrace_pcs[0] >= MAX_C_STACK_DEPTH {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
c_backtrace_pcs[0] += 1;
|
|
34
|
+
c_backtrace_pcs[c_backtrace_pcs[0]] = pc;
|
|
35
|
+
0
|
|
36
|
+
},
|
|
37
|
+
Some(Backtrace::backtrace_error_callback),
|
|
38
|
+
);
|
|
39
|
+
|
|
20
40
|
let mut sample = Sample {
|
|
21
41
|
ruby_thread,
|
|
22
42
|
timestamp: Instant::now(),
|
|
23
43
|
line_count: 0,
|
|
24
44
|
frames: [0; MAX_STACK_DEPTH],
|
|
25
45
|
linenos: [0; MAX_STACK_DEPTH],
|
|
46
|
+
c_backtrace_pcs,
|
|
26
47
|
};
|
|
27
48
|
unsafe {
|
|
28
49
|
sample.line_count = rb_profile_thread_frames(
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
1
2
|
use std::str::FromStr;
|
|
3
|
+
use std::time::Duration;
|
|
4
|
+
|
|
5
|
+
use rb_sys::VALUE;
|
|
2
6
|
|
|
3
7
|
#[derive(Clone, Debug)]
|
|
4
8
|
pub struct Configuration {
|
|
9
|
+
pub interval: Duration,
|
|
5
10
|
pub time_mode: TimeMode,
|
|
11
|
+
pub target_ruby_threads: HashSet<VALUE>,
|
|
12
|
+
pub track_new_threads: bool,
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
#[derive(Clone, Debug)]
|
|
@@ -23,7 +23,6 @@ pub struct TimerInstaller {
|
|
|
23
23
|
|
|
24
24
|
struct Internal {
|
|
25
25
|
configuration: Configuration,
|
|
26
|
-
target_ruby_threads: HashSet<VALUE>,
|
|
27
26
|
registered_pthread_ids: HashSet<libc::pthread_t>,
|
|
28
27
|
kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
|
|
29
28
|
profile: Arc<RwLock<Profile>>,
|
|
@@ -34,14 +33,11 @@ impl TimerInstaller {
|
|
|
34
33
|
// The callback should create a timer for the thread.
|
|
35
34
|
pub fn install_timer_to_ruby_threads(
|
|
36
35
|
configuration: Configuration,
|
|
37
|
-
ruby_threads: &HashSet<VALUE>,
|
|
38
36
|
profile: Arc<RwLock<Profile>>,
|
|
39
|
-
track_new_threads: bool,
|
|
40
37
|
) {
|
|
41
38
|
let registrar = Self {
|
|
42
39
|
internal: Box::new(Mutex::new(Internal {
|
|
43
|
-
configuration,
|
|
44
|
-
target_ruby_threads: ruby_threads.clone(),
|
|
40
|
+
configuration: configuration.clone(),
|
|
45
41
|
registered_pthread_ids: HashSet::new(),
|
|
46
42
|
kernel_thread_id_to_ruby_thread_map: HashMap::new(),
|
|
47
43
|
profile,
|
|
@@ -60,7 +56,7 @@ impl TimerInstaller {
|
|
|
60
56
|
rb_thread_create(Some(Self::do_nothing), null_mut());
|
|
61
57
|
};
|
|
62
58
|
|
|
63
|
-
if track_new_threads {
|
|
59
|
+
if configuration.track_new_threads {
|
|
64
60
|
unsafe {
|
|
65
61
|
rb_internal_thread_add_event_hook(
|
|
66
62
|
Some(Self::on_thread_start),
|
|
@@ -88,7 +84,11 @@ impl TimerInstaller {
|
|
|
88
84
|
|
|
89
85
|
// Check if the current thread is a target Ruby Thread
|
|
90
86
|
let current_ruby_thread: VALUE = unsafe { (*data).thread };
|
|
91
|
-
if !internal
|
|
87
|
+
if !internal
|
|
88
|
+
.configuration
|
|
89
|
+
.target_ruby_threads
|
|
90
|
+
.contains(¤t_ruby_thread)
|
|
91
|
+
{
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -129,7 +129,10 @@ impl TimerInstaller {
|
|
|
129
129
|
let mut internal = internal.lock().unwrap();
|
|
130
130
|
|
|
131
131
|
let current_ruby_thread: VALUE = unsafe { (*data).thread };
|
|
132
|
-
internal
|
|
132
|
+
internal
|
|
133
|
+
.configuration
|
|
134
|
+
.target_ruby_threads
|
|
135
|
+
.insert(current_ruby_thread);
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
// Creates a new POSIX timer which invocates sampling for the thread that called this function.
|
|
@@ -162,31 +165,35 @@ impl TimerInstaller {
|
|
|
162
165
|
// Pass required args to the signal handler
|
|
163
166
|
sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
|
|
164
167
|
|
|
165
|
-
// Create and configure timer to fire every
|
|
168
|
+
// Create and configure timer to fire every _interval_ ms of CPU time
|
|
166
169
|
let mut timer: libc::timer_t = unsafe { mem::zeroed() };
|
|
167
|
-
match configuration.time_mode {
|
|
168
|
-
crate::signal_scheduler::TimeMode::CpuTime =>
|
|
169
|
-
|
|
170
|
-
libc::timer_create(libc::CLOCK_THREAD_CPUTIME_ID, &mut sigevent, &mut timer)
|
|
171
|
-
};
|
|
172
|
-
if err != 0 {
|
|
173
|
-
panic!("timer_create failed: {}", err);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
crate::signal_scheduler::TimeMode::WallTime => {
|
|
177
|
-
todo!("WallTime is not supported yet");
|
|
178
|
-
}
|
|
170
|
+
let clockid = match configuration.time_mode {
|
|
171
|
+
crate::signal_scheduler::TimeMode::CpuTime => libc::CLOCK_THREAD_CPUTIME_ID,
|
|
172
|
+
crate::signal_scheduler::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
|
|
179
173
|
};
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let err = unsafe { libc::timer_settime(timer, 0, &
|
|
174
|
+
let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
|
|
175
|
+
if err != 0 {
|
|
176
|
+
panic!("timer_create failed: {}", err);
|
|
177
|
+
}
|
|
178
|
+
let itimerspec = Self::duration_to_itimerspec(&configuration.interval);
|
|
179
|
+
let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
|
|
186
180
|
if err != 0 {
|
|
187
181
|
panic!("timer_settime failed: {}", err);
|
|
188
182
|
}
|
|
189
183
|
|
|
190
184
|
log::debug!("timer registered for thread {}", current_pthread_id);
|
|
191
185
|
}
|
|
186
|
+
|
|
187
|
+
fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
|
|
188
|
+
let nanos = duration.as_nanos();
|
|
189
|
+
let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
|
|
190
|
+
let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
|
|
191
|
+
|
|
192
|
+
let mut its: libc::itimerspec = unsafe { mem::zeroed() };
|
|
193
|
+
its.it_interval.tv_sec = seconds_part;
|
|
194
|
+
its.it_interval.tv_nsec = nanos_part;
|
|
195
|
+
its.it_value.tv_sec = seconds_part;
|
|
196
|
+
its.it_value.tv_nsec = nanos_part;
|
|
197
|
+
its
|
|
198
|
+
}
|
|
192
199
|
}
|
|
@@ -11,8 +11,9 @@ use crate::sample::Sample;
|
|
|
11
11
|
|
|
12
12
|
use core::panic;
|
|
13
13
|
use std::collections::HashSet;
|
|
14
|
-
use std::ffi::{c_int, c_void, CString};
|
|
14
|
+
use std::ffi::{c_int, c_void, CStr, CString};
|
|
15
15
|
use std::mem::ManuallyDrop;
|
|
16
|
+
use std::str::FromStr;
|
|
16
17
|
use std::sync::{Arc, RwLock};
|
|
17
18
|
use std::thread;
|
|
18
19
|
use std::time::Duration;
|
|
@@ -24,7 +25,7 @@ use crate::util::*;
|
|
|
24
25
|
|
|
25
26
|
#[derive(Debug)]
|
|
26
27
|
pub struct SignalScheduler {
|
|
27
|
-
configuration: configuration::Configuration
|
|
28
|
+
configuration: Option<configuration::Configuration>,
|
|
28
29
|
profile: Option<Arc<RwLock<Profile>>>,
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -36,37 +37,100 @@ pub struct SignalHandlerArgs {
|
|
|
36
37
|
impl SignalScheduler {
|
|
37
38
|
fn new() -> Self {
|
|
38
39
|
Self {
|
|
39
|
-
configuration:
|
|
40
|
-
time_mode: TimeMode::CpuTime,
|
|
41
|
-
},
|
|
40
|
+
configuration: None,
|
|
42
41
|
profile: None,
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
fn
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
|
|
46
|
+
// Parse arguments
|
|
47
|
+
let kwargs: VALUE = Qnil.into();
|
|
48
|
+
unsafe {
|
|
49
|
+
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
|
|
50
|
+
};
|
|
51
|
+
let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
|
|
52
|
+
unsafe {
|
|
53
|
+
rb_get_kwargs(
|
|
54
|
+
kwargs,
|
|
55
|
+
[
|
|
56
|
+
rb_intern(cstr!("interval_ms")),
|
|
57
|
+
rb_intern(cstr!("threads")),
|
|
58
|
+
rb_intern(cstr!("time_mode")),
|
|
59
|
+
rb_intern(cstr!("track_new_threads")),
|
|
60
|
+
]
|
|
61
|
+
.as_mut_ptr(),
|
|
62
|
+
0,
|
|
63
|
+
4,
|
|
64
|
+
kwargs_values.as_mut_ptr(),
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
|
|
68
|
+
let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
|
|
69
|
+
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
|
|
70
|
+
eprintln!(
|
|
71
|
+
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
|
|
72
|
+
interval_ms
|
|
73
|
+
);
|
|
74
|
+
49
|
|
75
|
+
}))
|
|
76
|
+
} else {
|
|
77
|
+
Duration::from_millis(49)
|
|
78
|
+
};
|
|
79
|
+
let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
|
|
80
|
+
kwargs_values[1]
|
|
81
|
+
} else {
|
|
82
|
+
unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
|
|
83
|
+
};
|
|
84
|
+
let time_mode: configuration::TimeMode = if kwargs_values[2] != Qundef as VALUE {
|
|
85
|
+
let specified_mode = unsafe {
|
|
86
|
+
let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
|
|
87
|
+
let ptr = rb_string_value_ptr(&mut str);
|
|
88
|
+
CStr::from_ptr(ptr).to_str().unwrap()
|
|
89
|
+
};
|
|
90
|
+
configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
|
|
91
|
+
// Raise an ArgumentError
|
|
92
|
+
unsafe {
|
|
93
|
+
rb_raise(
|
|
94
|
+
rb_eArgError,
|
|
95
|
+
cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
} else {
|
|
100
|
+
configuration::TimeMode::CpuTime
|
|
101
|
+
};
|
|
102
|
+
let track_new_threads: bool = if kwargs_values[3] != Qundef as VALUE {
|
|
103
|
+
RTEST(kwargs_values[3])
|
|
104
|
+
} else {
|
|
105
|
+
false
|
|
106
|
+
};
|
|
57
107
|
|
|
58
108
|
let mut target_ruby_threads = HashSet::new();
|
|
59
109
|
unsafe {
|
|
60
|
-
for i in 0..RARRAY_LEN(
|
|
61
|
-
let ruby_thread: VALUE = rb_ary_entry(
|
|
110
|
+
for i in 0..RARRAY_LEN(threads) {
|
|
111
|
+
let ruby_thread: VALUE = rb_ary_entry(threads, i);
|
|
62
112
|
target_ruby_threads.insert(ruby_thread);
|
|
63
113
|
}
|
|
64
114
|
}
|
|
115
|
+
|
|
116
|
+
self.configuration = Some(Configuration {
|
|
117
|
+
interval,
|
|
118
|
+
target_ruby_threads,
|
|
119
|
+
time_mode,
|
|
120
|
+
track_new_threads,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
Qnil.into()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn start(&mut self, _rbself: VALUE) -> VALUE {
|
|
127
|
+
let profile = Arc::new(RwLock::new(Profile::new()));
|
|
128
|
+
self.start_profile_buffer_flusher_thread(&profile);
|
|
129
|
+
self.install_signal_handler();
|
|
130
|
+
|
|
65
131
|
TimerInstaller::install_timer_to_ruby_threads(
|
|
66
|
-
self.configuration.clone(),
|
|
67
|
-
&target_ruby_threads,
|
|
132
|
+
self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
|
|
68
133
|
Arc::clone(&profile),
|
|
69
|
-
track_new_threads,
|
|
70
134
|
);
|
|
71
135
|
|
|
72
136
|
self.profile = Some(profile);
|
|
@@ -132,7 +196,7 @@ impl SignalScheduler {
|
|
|
132
196
|
}
|
|
133
197
|
};
|
|
134
198
|
|
|
135
|
-
let sample = Sample::capture(args.context_ruby_thread); // NOT async-signal-safe
|
|
199
|
+
let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
|
|
136
200
|
if profile.temporary_sample_buffer.push(sample).is_err() {
|
|
137
201
|
log::debug!("Temporary sample buffer full. Dropping sample.");
|
|
138
202
|
}
|
|
@@ -156,13 +220,18 @@ impl SignalScheduler {
|
|
|
156
220
|
|
|
157
221
|
// Ruby Methods
|
|
158
222
|
|
|
159
|
-
pub unsafe extern "C" fn
|
|
223
|
+
pub unsafe extern "C" fn rb_initialize(
|
|
224
|
+
argc: c_int,
|
|
225
|
+
argv: *const VALUE,
|
|
160
226
|
rbself: VALUE,
|
|
161
|
-
ruby_threads: VALUE,
|
|
162
|
-
track_new_threads: VALUE,
|
|
163
227
|
) -> VALUE {
|
|
164
228
|
let mut collector = unsafe { Self::get_struct_from(rbself) };
|
|
165
|
-
collector.
|
|
229
|
+
collector.initialize(argc, argv, rbself)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
|
|
233
|
+
let mut collector = unsafe { Self::get_struct_from(rbself) };
|
|
234
|
+
collector.start(rbself)
|
|
166
235
|
}
|
|
167
236
|
|
|
168
237
|
pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
|