pf2 0.6.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Cargo.lock +6 -26
- data/crates/backtrace-sys2/build.rs +1 -4
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +116 -31
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +295 -141
- data/crates/backtrace-sys2/src/libbacktrace/README.md +11 -1
- data/crates/backtrace-sys2/src/libbacktrace/alloc.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/atomic.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +12 -12
- data/crates/backtrace-sys2/src/libbacktrace/btest.c +24 -8
- data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +162 -53
- data/crates/backtrace-sys2/src/libbacktrace/config.h.in +3 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure +255 -66
- data/crates/backtrace-sys2/src/libbacktrace/configure.ac +27 -8
- data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +37 -30
- data/crates/backtrace-sys2/src/libbacktrace/edtest.c +2 -2
- data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/elf.c +98 -76
- data/crates/backtrace-sys2/src/libbacktrace/fileline.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +2 -2
- data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/internal.h +41 -2
- data/crates/backtrace-sys2/src/libbacktrace/macho.c +25 -19
- data/crates/backtrace-sys2/src/libbacktrace/mmap.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/mtest.c +4 -4
- data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +192 -26
- data/crates/backtrace-sys2/src/libbacktrace/posix.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/print.c +41 -16
- data/crates/backtrace-sys2/src/libbacktrace/read.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/simple.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/sort.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/state.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/stest.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/test_format.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/testlib.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/testlib.h +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/ttest.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/unittest.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/unknown.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +43 -32
- data/crates/backtrace-sys2/src/libbacktrace/xztest.c +2 -2
- data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +1 -1
- data/crates/backtrace-sys2/src/libbacktrace/ztest.c +1 -1
- data/ext/pf2/Cargo.toml +1 -1
- data/ext/pf2/src/profile_serializer.rs +5 -12
- data/ext/pf2/src/ringbuffer.rs +1 -3
- data/ext/pf2/src/ruby_init.rs +1 -4
- data/ext/pf2/src/sample.rs +1 -0
- data/ext/pf2/src/serialization/profile.rs +2 -1
- data/ext/pf2/src/serialization/serializer.rs +227 -48
- data/ext/pf2/src/session/new_thread_watcher.rs +1 -1
- data/ext/pf2/src/session/ruby_object.rs +1 -5
- data/ext/pf2/src/session.rs +5 -15
- data/ext/pf2/src/signal_scheduler.rs +9 -10
- data/ext/pf2/src/timer_thread_scheduler.rs +8 -6
- data/lib/pf2/cli.rb +2 -0
- data/lib/pf2/reporter/firefox_profiler.rb +2 -0
- data/lib/pf2/reporter/stack_weaver.rb +81 -0
- data/lib/pf2/reporter.rb +3 -4
- data/lib/pf2/serve.rb +2 -0
- data/lib/pf2/session.rb +2 -0
- data/lib/pf2/version.rb +3 -1
- data/lib/pf2.rb +4 -1
- data/rust-toolchain.toml +2 -0
- data/rustfmt.toml +1 -0
- metadata +10 -12
- data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +0 -7436
- data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +0 -369
- data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +0 -123
- data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +0 -23
- data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +0 -98
@@ -1,9 +1,12 @@
|
|
1
|
-
use std::ffi::CStr;
|
1
|
+
use std::ffi::{c_char, CStr, CString};
|
2
2
|
|
3
3
|
use rb_sys::*;
|
4
4
|
|
5
|
-
use super::profile::{
|
6
|
-
|
5
|
+
use super::profile::{
|
6
|
+
Function, FunctionImplementation, FunctionIndex, Location, LocationIndex, Profile, Sample,
|
7
|
+
};
|
8
|
+
use crate::backtrace::Backtrace;
|
9
|
+
use crate::util::{cstr, RTEST};
|
7
10
|
|
8
11
|
pub struct ProfileSerializer2 {
|
9
12
|
profile: Profile,
|
@@ -22,77 +25,66 @@ impl ProfileSerializer2 {
|
|
22
25
|
}
|
23
26
|
}
|
24
27
|
|
25
|
-
pub fn serialize(&mut self, source: &crate::profile::Profile)
|
28
|
+
pub fn serialize(&mut self, source: &crate::profile::Profile) {
|
26
29
|
// Fill in meta fields
|
27
|
-
self.profile.start_timestamp_ns =
|
28
|
-
.start_timestamp
|
29
|
-
|
30
|
-
.unwrap()
|
31
|
-
.as_nanos();
|
32
|
-
self.profile.duration_ns = source
|
33
|
-
.end_instant
|
34
|
-
.unwrap()
|
35
|
-
.duration_since(source.start_instant)
|
36
|
-
.as_nanos();
|
30
|
+
self.profile.start_timestamp_ns =
|
31
|
+
source.start_timestamp.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
32
|
+
self.profile.duration_ns =
|
33
|
+
source.end_instant.unwrap().duration_since(source.start_instant).as_nanos();
|
37
34
|
|
38
35
|
// Create a Sample for each sample collected
|
39
36
|
for sample in source.samples.iter() {
|
40
|
-
let mut stack: Vec<LocationIndex> = vec![];
|
41
|
-
|
42
37
|
// Iterate over the Ruby stack
|
38
|
+
let mut stack: Vec<LocationIndex> = vec![];
|
43
39
|
let ruby_stack_depth = sample.line_count;
|
44
40
|
for i in 0..ruby_stack_depth {
|
45
41
|
let frame: VALUE = sample.frames[i as usize];
|
46
42
|
let lineno: i32 = sample.linenos[i as usize];
|
43
|
+
let function = Self::extract_function_from_ruby_frame(frame);
|
47
44
|
|
48
|
-
|
49
|
-
let location_index = self.
|
50
|
-
|
45
|
+
let function_index = self.function_index_for(function);
|
46
|
+
let location_index = self.location_index_for(function_index, lineno);
|
51
47
|
stack.push(location_index);
|
52
48
|
}
|
53
49
|
|
50
|
+
// Iterate over the native stack
|
51
|
+
let mut native_stack: Vec<LocationIndex> = vec![];
|
52
|
+
let native_stack_depth = sample.c_backtrace_pcs[0];
|
53
|
+
for i in 1..(native_stack_depth - 1) {
|
54
|
+
let pc = sample.c_backtrace_pcs[i];
|
55
|
+
let function = Self::extract_function_from_native_pc(pc, source);
|
56
|
+
|
57
|
+
let function_index = self.function_index_for(function);
|
58
|
+
let location_index = self.location_index_for(function_index, 0);
|
59
|
+
native_stack.push(location_index);
|
60
|
+
}
|
61
|
+
|
54
62
|
self.profile.samples.push(Sample {
|
55
63
|
stack,
|
64
|
+
native_stack,
|
56
65
|
ruby_thread_id: Some(sample.ruby_thread),
|
57
66
|
});
|
58
67
|
}
|
59
|
-
|
60
|
-
serde_json::to_string(&self.profile).unwrap()
|
61
68
|
}
|
62
69
|
|
63
|
-
///
|
70
|
+
/// Returns the index of the function in `functions`.
|
64
71
|
/// Calling this method will modify `self.profile` in place.
|
65
|
-
|
66
|
-
|
67
|
-
fn process_ruby_frame(&mut self, frame: VALUE, lineno: i32) -> LocationIndex {
|
68
|
-
// Build a Function corresponding to the frame, and get the index in `functions`
|
69
|
-
let function = Self::extract_function_from_frame(frame);
|
70
|
-
let function_index = match self
|
71
|
-
.profile
|
72
|
-
.functions
|
73
|
-
.iter_mut()
|
74
|
-
.position(|f| *f == function)
|
75
|
-
{
|
72
|
+
fn function_index_for(&mut self, function: Function) -> FunctionIndex {
|
73
|
+
match self.profile.functions.iter_mut().position(|f| *f == function) {
|
76
74
|
Some(index) => index,
|
77
75
|
None => {
|
78
76
|
self.profile.functions.push(function);
|
79
77
|
self.profile.functions.len() - 1
|
80
78
|
}
|
81
|
-
}
|
79
|
+
}
|
80
|
+
}
|
82
81
|
|
82
|
+
/// Returns the index of the location in `locations`.
|
83
|
+
/// Calling this method will modify `self.profile` in place.
|
84
|
+
fn location_index_for(&mut self, function_index: FunctionIndex, lineno: i32) -> LocationIndex {
|
83
85
|
// Build a Location based on (1) the Function and (2) the actual line hit during sampling.
|
84
|
-
let location = Location {
|
85
|
-
|
86
|
-
lineno,
|
87
|
-
address: None,
|
88
|
-
};
|
89
|
-
// Get the index of the location in `locations`
|
90
|
-
match self
|
91
|
-
.profile
|
92
|
-
.locations
|
93
|
-
.iter_mut()
|
94
|
-
.position(|l| *l == location)
|
95
|
-
{
|
86
|
+
let location = Location { function_index, lineno, address: None };
|
87
|
+
match self.profile.locations.iter_mut().position(|l| *l == location) {
|
96
88
|
Some(index) => index,
|
97
89
|
None => {
|
98
90
|
self.profile.locations.push(location);
|
@@ -101,7 +93,8 @@ impl ProfileSerializer2 {
|
|
101
93
|
}
|
102
94
|
}
|
103
95
|
|
104
|
-
|
96
|
+
/// Build a Function from a Ruby frame.
|
97
|
+
fn extract_function_from_ruby_frame(frame: VALUE) -> Function {
|
105
98
|
unsafe {
|
106
99
|
let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame);
|
107
100
|
let frame_full_label: Option<String> = if RTEST(frame_full_label) {
|
@@ -134,13 +127,199 @@ impl ProfileSerializer2 {
|
|
134
127
|
None
|
135
128
|
};
|
136
129
|
|
130
|
+
let start_address = Self::get_underlying_c_function_address(frame);
|
131
|
+
|
137
132
|
Function {
|
138
133
|
implementation: FunctionImplementation::Ruby,
|
139
134
|
name: frame_full_label,
|
140
135
|
filename: frame_path,
|
141
136
|
start_lineno: frame_first_lineno,
|
142
|
-
start_address
|
137
|
+
start_address,
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
fn get_underlying_c_function_address(frame: VALUE) -> Option<usize> {
|
143
|
+
unsafe {
|
144
|
+
let cme = frame as *mut crate::ruby_internal_apis::rb_callable_method_entry_struct;
|
145
|
+
let cme = &*cme; // *mut to reference
|
146
|
+
|
147
|
+
if (*(cme.def)).type_ == 1 {
|
148
|
+
// The cme is a Cfunc
|
149
|
+
Some((*(cme.def)).cfunc.func as usize)
|
150
|
+
} else {
|
151
|
+
// The cme is an ISeq (Ruby code) or some other type
|
152
|
+
None
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
/// Build a Function from a PC (program counter) obtained by libbacktrace.
|
158
|
+
fn extract_function_from_native_pc(pc: usize, source: &crate::profile::Profile) -> Function {
|
159
|
+
// Obtain the function name and address using libbacktrace
|
160
|
+
let mut function: Option<Function> = None;
|
161
|
+
Backtrace::backtrace_syminfo(
|
162
|
+
&source.backtrace_state,
|
163
|
+
pc,
|
164
|
+
|_pc: usize, symname: *const c_char, symval: usize, _symsize: usize| unsafe {
|
165
|
+
function = Some(Function {
|
166
|
+
implementation: FunctionImplementation::Native,
|
167
|
+
name: if symname.is_null() {
|
168
|
+
None
|
169
|
+
} else {
|
170
|
+
Some(CStr::from_ptr(symname).to_str().unwrap().to_owned())
|
171
|
+
},
|
172
|
+
filename: None,
|
173
|
+
start_lineno: None,
|
174
|
+
start_address: Some(symval),
|
175
|
+
});
|
176
|
+
},
|
177
|
+
Some(Backtrace::backtrace_error_callback),
|
178
|
+
);
|
179
|
+
function.unwrap()
|
180
|
+
}
|
181
|
+
|
182
|
+
pub fn to_ruby_hash(&self) -> VALUE {
|
183
|
+
unsafe {
|
184
|
+
let hash: VALUE = rb_hash_new();
|
185
|
+
|
186
|
+
// profile[:start_timestamp_ns]
|
187
|
+
rb_hash_aset(
|
188
|
+
hash,
|
189
|
+
rb_id2sym(rb_intern(cstr!("start_timestamp_ns"))),
|
190
|
+
rb_int2inum(self.profile.start_timestamp_ns as isize),
|
191
|
+
);
|
192
|
+
// profile[:duration_ns]
|
193
|
+
rb_hash_aset(
|
194
|
+
hash,
|
195
|
+
rb_id2sym(rb_intern(cstr!("duration_ns"))),
|
196
|
+
rb_int2inum(self.profile.duration_ns as isize),
|
197
|
+
);
|
198
|
+
|
199
|
+
// profile[:samples]
|
200
|
+
let samples: VALUE = rb_ary_new();
|
201
|
+
for sample in self.profile.samples.iter() {
|
202
|
+
// sample[:stack]
|
203
|
+
let stack: VALUE = rb_ary_new();
|
204
|
+
for &location_index in sample.stack.iter() {
|
205
|
+
rb_ary_push(stack, rb_int2inum(location_index as isize));
|
206
|
+
}
|
207
|
+
// sample[:native_stack]
|
208
|
+
let native_stack: VALUE = rb_ary_new();
|
209
|
+
for &location_index in sample.native_stack.iter() {
|
210
|
+
rb_ary_push(native_stack, rb_int2inum(location_index as isize));
|
211
|
+
}
|
212
|
+
// sample[:ruby_thread_id]
|
213
|
+
let ruby_thread_id = if let Some(ruby_thread_id) = sample.ruby_thread_id {
|
214
|
+
rb_int2inum(ruby_thread_id as isize)
|
215
|
+
} else {
|
216
|
+
Qnil as VALUE
|
217
|
+
};
|
218
|
+
|
219
|
+
let sample_hash: VALUE = rb_hash_new();
|
220
|
+
rb_hash_aset(sample_hash, rb_id2sym(rb_intern(cstr!("stack"))), stack);
|
221
|
+
rb_hash_aset(
|
222
|
+
sample_hash,
|
223
|
+
rb_id2sym(rb_intern(cstr!("native_stack"))),
|
224
|
+
native_stack,
|
225
|
+
);
|
226
|
+
rb_hash_aset(
|
227
|
+
sample_hash,
|
228
|
+
rb_id2sym(rb_intern(cstr!("ruby_thread_id"))),
|
229
|
+
ruby_thread_id,
|
230
|
+
);
|
231
|
+
|
232
|
+
rb_ary_push(samples, sample_hash);
|
233
|
+
}
|
234
|
+
rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("samples"))), samples);
|
235
|
+
|
236
|
+
// profile[:locations]
|
237
|
+
let locations = rb_ary_new();
|
238
|
+
for location in self.profile.locations.iter() {
|
239
|
+
let location_hash: VALUE = rb_hash_new();
|
240
|
+
// location[:function_index]
|
241
|
+
rb_hash_aset(
|
242
|
+
location_hash,
|
243
|
+
rb_id2sym(rb_intern(cstr!("function_index"))),
|
244
|
+
rb_int2inum(location.function_index as isize),
|
245
|
+
);
|
246
|
+
// location[:lineno]
|
247
|
+
rb_hash_aset(
|
248
|
+
location_hash,
|
249
|
+
rb_id2sym(rb_intern(cstr!("lineno"))),
|
250
|
+
rb_int2inum(location.lineno as isize),
|
251
|
+
);
|
252
|
+
// location[:address]
|
253
|
+
rb_hash_aset(
|
254
|
+
location_hash,
|
255
|
+
rb_id2sym(rb_intern(cstr!("address"))),
|
256
|
+
if let Some(address) = location.address {
|
257
|
+
rb_int2inum(address as isize)
|
258
|
+
} else {
|
259
|
+
Qnil as VALUE
|
260
|
+
},
|
261
|
+
);
|
262
|
+
rb_ary_push(locations, location_hash);
|
143
263
|
}
|
264
|
+
rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("locations"))), locations);
|
265
|
+
|
266
|
+
// profile[:functions]
|
267
|
+
let functions = rb_ary_new();
|
268
|
+
for function in self.profile.functions.iter() {
|
269
|
+
let function_hash: VALUE = rb_hash_new();
|
270
|
+
// function[:implementation]
|
271
|
+
rb_hash_aset(
|
272
|
+
function_hash,
|
273
|
+
rb_id2sym(rb_intern(cstr!("implementation"))),
|
274
|
+
match function.implementation {
|
275
|
+
FunctionImplementation::Ruby => rb_id2sym(rb_intern(cstr!("ruby"))),
|
276
|
+
FunctionImplementation::Native => rb_id2sym(rb_intern(cstr!("native"))),
|
277
|
+
},
|
278
|
+
);
|
279
|
+
|
280
|
+
// function[:name]
|
281
|
+
let name: VALUE = match &function.name {
|
282
|
+
Some(name) => {
|
283
|
+
let cstring = CString::new(name.as_str()).unwrap();
|
284
|
+
rb_str_new_cstr(cstring.as_ptr())
|
285
|
+
}
|
286
|
+
None => Qnil as VALUE,
|
287
|
+
};
|
288
|
+
rb_hash_aset(function_hash, rb_id2sym(rb_intern(cstr!("name"))), name);
|
289
|
+
// function[:filename]
|
290
|
+
let filename: VALUE = match &function.filename {
|
291
|
+
Some(filename) => {
|
292
|
+
let cstring = CString::new(filename.as_str()).unwrap();
|
293
|
+
rb_str_new_cstr(cstring.as_ptr())
|
294
|
+
}
|
295
|
+
None => Qnil as VALUE,
|
296
|
+
};
|
297
|
+
rb_hash_aset(function_hash, rb_id2sym(rb_intern(cstr!("filename"))), filename);
|
298
|
+
// function[:start_lineno]
|
299
|
+
rb_hash_aset(
|
300
|
+
function_hash,
|
301
|
+
rb_id2sym(rb_intern(cstr!("start_lineno"))),
|
302
|
+
if let Some(start_lineno) = function.start_lineno {
|
303
|
+
rb_int2inum(start_lineno as isize)
|
304
|
+
} else {
|
305
|
+
Qnil as VALUE
|
306
|
+
},
|
307
|
+
);
|
308
|
+
// function[:start_address]
|
309
|
+
rb_hash_aset(
|
310
|
+
function_hash,
|
311
|
+
rb_id2sym(rb_intern(cstr!("start_address"))),
|
312
|
+
if let Some(start_address) = function.start_address {
|
313
|
+
rb_int2inum(start_address as isize)
|
314
|
+
} else {
|
315
|
+
Qnil as VALUE
|
316
|
+
},
|
317
|
+
);
|
318
|
+
rb_ary_push(functions, function_hash);
|
319
|
+
}
|
320
|
+
rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("functions"))), functions);
|
321
|
+
|
322
|
+
hash
|
144
323
|
}
|
145
324
|
}
|
146
325
|
}
|
@@ -54,7 +54,7 @@ impl NewThreadWatcher {
|
|
54
54
|
|
55
55
|
unsafe extern "C" fn on_thread_resume(
|
56
56
|
_flag: rb_event_flag_t,
|
57
|
-
data: *const
|
57
|
+
data: *const rb_internal_thread_event_data_t,
|
58
58
|
custom_data: *mut c_void,
|
59
59
|
) {
|
60
60
|
let ruby_thread: VALUE = unsafe { (*data).thread };
|
@@ -55,11 +55,7 @@ impl SessionRubyObject {
|
|
55
55
|
let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
|
56
56
|
let rb_cSession = rb_define_class_under(rb_mPf2, cstr!("Session"), rb_cObject);
|
57
57
|
// Wrap the struct into a Ruby object
|
58
|
-
rb_data_typed_object_wrap(
|
59
|
-
rb_cSession,
|
60
|
-
Box::into_raw(obj) as *mut c_void,
|
61
|
-
addr_of!(RBDATA),
|
62
|
-
)
|
58
|
+
rb_data_typed_object_wrap(rb_cSession, Box::into_raw(obj) as *mut c_void, addr_of!(RBDATA))
|
63
59
|
}
|
64
60
|
|
65
61
|
unsafe extern "C" fn dmark(ptr: *mut c_void) {
|
data/ext/pf2/src/session.rs
CHANGED
@@ -91,10 +91,9 @@ impl Session {
|
|
91
91
|
configuration::Scheduler::Signal => {
|
92
92
|
Arc::new(SignalScheduler::new(&configuration, Arc::clone(&profile)))
|
93
93
|
}
|
94
|
-
configuration::Scheduler::TimerThread =>
|
95
|
-
&configuration,
|
96
|
-
|
97
|
-
)),
|
94
|
+
configuration::Scheduler::TimerThread => {
|
95
|
+
Arc::new(TimerThreadScheduler::new(&configuration, Arc::clone(&profile)))
|
96
|
+
}
|
98
97
|
};
|
99
98
|
|
100
99
|
let running = Arc::new(AtomicBool::new(false));
|
@@ -113,13 +112,7 @@ impl Session {
|
|
113
112
|
configuration::Threads::Targeted(_) => None,
|
114
113
|
};
|
115
114
|
|
116
|
-
Session {
|
117
|
-
configuration,
|
118
|
-
scheduler,
|
119
|
-
profile,
|
120
|
-
running,
|
121
|
-
new_thread_watcher,
|
122
|
-
}
|
115
|
+
Session { configuration, scheduler, profile, running, new_thread_watcher }
|
123
116
|
}
|
124
117
|
|
125
118
|
fn parse_option_interval_ms(value: VALUE) -> Duration {
|
@@ -202,10 +195,7 @@ impl Session {
|
|
202
195
|
// Raise an ArgumentError if the scheduler is not supported on the current platform
|
203
196
|
if !cfg!(target_os = "linux") && scheduler == configuration::Scheduler::Signal {
|
204
197
|
unsafe {
|
205
|
-
rb_raise(
|
206
|
-
rb_eArgError,
|
207
|
-
cstr!("Signal scheduler is not supported on this platform."),
|
208
|
-
)
|
198
|
+
rb_raise(rb_eArgError, cstr!("Signal scheduler is not supported on this platform."))
|
209
199
|
}
|
210
200
|
}
|
211
201
|
scheduler
|
@@ -58,13 +58,15 @@ impl Scheduler for SignalScheduler {
|
|
58
58
|
let profile = self.profile.try_read().unwrap();
|
59
59
|
log::debug!("Number of samples: {}", profile.samples.len());
|
60
60
|
|
61
|
-
|
62
|
-
ProfileSerializer2::new()
|
61
|
+
if self.configuration.use_experimental_serializer {
|
62
|
+
let mut ser = ProfileSerializer2::new();
|
63
|
+
ser.serialize(&profile);
|
64
|
+
ser.to_ruby_hash()
|
63
65
|
} else {
|
64
|
-
ProfileSerializer::serialize(&profile)
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
let serialized = ProfileSerializer::serialize(&profile);
|
67
|
+
let string = CString::new(serialized).unwrap();
|
68
|
+
unsafe { rb_str_new_cstr(string.as_ptr()) }
|
69
|
+
}
|
68
70
|
}
|
69
71
|
|
70
72
|
fn on_new_thread(&self, thread: VALUE) {
|
@@ -94,10 +96,7 @@ impl Scheduler for SignalScheduler {
|
|
94
96
|
|
95
97
|
impl SignalScheduler {
|
96
98
|
pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
|
97
|
-
Self {
|
98
|
-
configuration: configuration.clone(),
|
99
|
-
profile,
|
100
|
-
}
|
99
|
+
Self { configuration: configuration.clone(), profile }
|
101
100
|
}
|
102
101
|
|
103
102
|
// Install signal handler for profiling events to the current process.
|
@@ -73,13 +73,15 @@ impl Scheduler for TimerThreadScheduler {
|
|
73
73
|
let profile = self.profile.try_read().unwrap();
|
74
74
|
log::debug!("Number of samples: {}", profile.samples.len());
|
75
75
|
|
76
|
-
|
77
|
-
ProfileSerializer2::new()
|
76
|
+
if self.configuration.use_experimental_serializer {
|
77
|
+
let mut ser = ProfileSerializer2::new();
|
78
|
+
ser.serialize(&profile);
|
79
|
+
ser.to_ruby_hash()
|
78
80
|
} else {
|
79
|
-
ProfileSerializer::serialize(&profile)
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
let serialized = ProfileSerializer::serialize(&profile);
|
82
|
+
let string = CString::new(serialized).unwrap();
|
83
|
+
unsafe { rb_str_new_cstr(string.as_ptr()) }
|
84
|
+
}
|
83
85
|
}
|
84
86
|
|
85
87
|
fn on_new_thread(&self, _thread: VALUE) {
|
data/lib/pf2/cli.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pf2
|
4
|
+
module Reporter
|
5
|
+
class StackWeaver
|
6
|
+
def initialize(profile)
|
7
|
+
@profile = profile
|
8
|
+
end
|
9
|
+
|
10
|
+
def weave(ruby_stack, native_stack)
|
11
|
+
ruby_stack = ruby_stack.dup
|
12
|
+
native_stack = native_stack.dup
|
13
|
+
|
14
|
+
weaved_stack = []
|
15
|
+
|
16
|
+
current_stack = :native
|
17
|
+
loop do
|
18
|
+
break if ruby_stack.size == 0 && native_stack.size == 0
|
19
|
+
case current_stack
|
20
|
+
when :ruby
|
21
|
+
if ruby_stack.size == 0 # We've reached the end of the Ruby stack
|
22
|
+
current_stack = :native
|
23
|
+
next
|
24
|
+
end
|
25
|
+
|
26
|
+
location_index = ruby_stack.pop
|
27
|
+
weaved_stack.unshift(location_index)
|
28
|
+
|
29
|
+
current_stack = :native if should_switch_to_native?(location_index, native_stack.dup)
|
30
|
+
|
31
|
+
when :native
|
32
|
+
if native_stack.size == 0 # We've reached the end of the native stack
|
33
|
+
current_stack = :ruby
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
location_index = native_stack.pop
|
38
|
+
weaved_stack.unshift(location_index)
|
39
|
+
|
40
|
+
current_stack = :ruby if should_switch_to_ruby?(location_index)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
weaved_stack
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [Integer] location_index
|
48
|
+
# @param [Array<Integer>] native_stack_remainder
|
49
|
+
def should_switch_to_native?(location_index, native_stack_remainder)
|
50
|
+
location = @profile[:locations][location_index]
|
51
|
+
function = @profile[:functions][location[:function_index]]
|
52
|
+
raise if function[:implementation] != :ruby # assert
|
53
|
+
|
54
|
+
# Is the current Ruby function a cfunc?
|
55
|
+
return false if function[:start_address] == nil
|
56
|
+
|
57
|
+
# Does a corresponding native function exist in the remainder of the native stack?
|
58
|
+
loop do
|
59
|
+
break if native_stack_remainder.size == 0
|
60
|
+
n_location_index = native_stack_remainder.pop
|
61
|
+
n_location = @profile[:locations][n_location_index]
|
62
|
+
n_function = @profile[:functions][n_location[:function_index]]
|
63
|
+
|
64
|
+
return true if function[:start_address] == n_function[:start_address]
|
65
|
+
end
|
66
|
+
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
def should_switch_to_ruby?(location_index)
|
71
|
+
location = @profile[:locations][location_index]
|
72
|
+
function = @profile[:functions][location[:function_index]]
|
73
|
+
raise if function[:implementation] != :native # assert
|
74
|
+
|
75
|
+
# If the next function is a vm_exec_core() (= VM_EXEC in vm_exec.h),
|
76
|
+
# we switch to the Ruby stack.
|
77
|
+
function[:name] == 'vm_exec_core'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/pf2/reporter.rb
CHANGED
data/lib/pf2/serve.rb
CHANGED
data/lib/pf2/session.rb
CHANGED
data/lib/pf2/version.rb
CHANGED
data/lib/pf2.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'pf2/pf2'
|
2
4
|
require_relative 'pf2/session'
|
3
5
|
require_relative 'pf2/version'
|
@@ -18,7 +20,8 @@ module Pf2
|
|
18
20
|
raise ArgumentError, "block required" unless block_given?
|
19
21
|
start(threads: Thread.list)
|
20
22
|
yield
|
21
|
-
stop
|
23
|
+
result = stop
|
22
24
|
@@session = nil # let GC clean up the session
|
25
|
+
result
|
23
26
|
end
|
24
27
|
end
|
data/rust-toolchain.toml
ADDED
data/rustfmt.toml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
use_small_heuristics = "Max"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pf2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daisuke Aritomo
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-01-04 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rake-compiler
|
@@ -27,16 +27,16 @@ dependencies:
|
|
27
27
|
name: rb_sys
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
|
-
- -
|
30
|
+
- - '='
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 0.9.
|
32
|
+
version: 0.9.105
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- -
|
37
|
+
- - '='
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0.9.
|
39
|
+
version: 0.9.105
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: webrick
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,12 +126,7 @@ files:
|
|
126
126
|
- crates/backtrace-sys2/src/libbacktrace/install-sh
|
127
127
|
- crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c
|
128
128
|
- crates/backtrace-sys2/src/libbacktrace/internal.h
|
129
|
-
- crates/backtrace-sys2/src/libbacktrace/libtool.m4
|
130
129
|
- crates/backtrace-sys2/src/libbacktrace/ltmain.sh
|
131
|
-
- crates/backtrace-sys2/src/libbacktrace/ltoptions.m4
|
132
|
-
- crates/backtrace-sys2/src/libbacktrace/ltsugar.m4
|
133
|
-
- crates/backtrace-sys2/src/libbacktrace/ltversion.m4
|
134
|
-
- crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4
|
135
130
|
- crates/backtrace-sys2/src/libbacktrace/macho.c
|
136
131
|
- crates/backtrace-sys2/src/libbacktrace/missing
|
137
132
|
- crates/backtrace-sys2/src/libbacktrace/mmap.c
|
@@ -187,9 +182,12 @@ files:
|
|
187
182
|
- lib/pf2/cli.rb
|
188
183
|
- lib/pf2/reporter.rb
|
189
184
|
- lib/pf2/reporter/firefox_profiler.rb
|
185
|
+
- lib/pf2/reporter/stack_weaver.rb
|
190
186
|
- lib/pf2/serve.rb
|
191
187
|
- lib/pf2/session.rb
|
192
188
|
- lib/pf2/version.rb
|
189
|
+
- rust-toolchain.toml
|
190
|
+
- rustfmt.toml
|
193
191
|
homepage: https://github.com/osyoyu/pf2
|
194
192
|
licenses:
|
195
193
|
- MIT
|
@@ -212,7 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
210
|
- !ruby/object:Gem::Version
|
213
211
|
version: '0'
|
214
212
|
requirements: []
|
215
|
-
rubygems_version: 3.6.
|
213
|
+
rubygems_version: 3.6.2
|
216
214
|
specification_version: 4
|
217
215
|
summary: Yet another Ruby profiler
|
218
216
|
test_files: []
|