pf2 0.6.0 → 0.7.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 +10 -0
- data/Cargo.lock +7 -27
- 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/rustfmt.toml +1 -0
- metadata +9 -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/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.0
|
|
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-02 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,11 @@ 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
|
+
- rustfmt.toml
|
|
193
190
|
homepage: https://github.com/osyoyu/pf2
|
|
194
191
|
licenses:
|
|
195
192
|
- MIT
|
|
@@ -212,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
212
209
|
- !ruby/object:Gem::Version
|
|
213
210
|
version: '0'
|
|
214
211
|
requirements: []
|
|
215
|
-
rubygems_version: 3.6.
|
|
212
|
+
rubygems_version: 3.6.2
|
|
216
213
|
specification_version: 4
|
|
217
214
|
summary: Yet another Ruby profiler
|
|
218
215
|
test_files: []
|