pf2 0.5.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Cargo.lock +7 -27
- data/README.md +1 -1
- 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/lib.rs +1 -0
- data/ext/pf2/src/profile.rs +7 -3
- data/ext/pf2/src/profile_serializer.rs +6 -13
- 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 +47 -0
- data/ext/pf2/src/serialization/serializer.rs +325 -0
- data/ext/pf2/src/serialization.rs +2 -0
- data/ext/pf2/src/session/configuration.rs +2 -1
- 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 +20 -19
- data/ext/pf2/src/signal_scheduler.rs +12 -7
- data/ext/pf2/src/timer_thread_scheduler.rs +11 -3
- data/lib/pf2/cli.rb +3 -1
- data/lib/pf2/reporter/firefox_profiler.rb +397 -0
- data/lib/pf2/reporter/stack_weaver.rb +81 -0
- data/lib/pf2/reporter.rb +3 -392
- data/lib/pf2/serve.rb +3 -1
- 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 +13 -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
@@ -0,0 +1,325 @@
|
|
1
|
+
use std::ffi::{c_char, CStr, CString};
|
2
|
+
|
3
|
+
use rb_sys::*;
|
4
|
+
|
5
|
+
use super::profile::{
|
6
|
+
Function, FunctionImplementation, FunctionIndex, Location, LocationIndex, Profile, Sample,
|
7
|
+
};
|
8
|
+
use crate::backtrace::Backtrace;
|
9
|
+
use crate::util::{cstr, RTEST};
|
10
|
+
|
11
|
+
pub struct ProfileSerializer2 {
|
12
|
+
profile: Profile,
|
13
|
+
}
|
14
|
+
|
15
|
+
impl ProfileSerializer2 {
|
16
|
+
pub fn new() -> ProfileSerializer2 {
|
17
|
+
ProfileSerializer2 {
|
18
|
+
profile: Profile {
|
19
|
+
start_timestamp_ns: 0,
|
20
|
+
duration_ns: 0,
|
21
|
+
samples: vec![],
|
22
|
+
locations: vec![],
|
23
|
+
functions: vec![],
|
24
|
+
},
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
pub fn serialize(&mut self, source: &crate::profile::Profile) {
|
29
|
+
// Fill in meta fields
|
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();
|
34
|
+
|
35
|
+
// Create a Sample for each sample collected
|
36
|
+
for sample in source.samples.iter() {
|
37
|
+
// Iterate over the Ruby stack
|
38
|
+
let mut stack: Vec<LocationIndex> = vec![];
|
39
|
+
let ruby_stack_depth = sample.line_count;
|
40
|
+
for i in 0..ruby_stack_depth {
|
41
|
+
let frame: VALUE = sample.frames[i as usize];
|
42
|
+
let lineno: i32 = sample.linenos[i as usize];
|
43
|
+
let function = Self::extract_function_from_ruby_frame(frame);
|
44
|
+
|
45
|
+
let function_index = self.function_index_for(function);
|
46
|
+
let location_index = self.location_index_for(function_index, lineno);
|
47
|
+
stack.push(location_index);
|
48
|
+
}
|
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
|
+
|
62
|
+
self.profile.samples.push(Sample {
|
63
|
+
stack,
|
64
|
+
native_stack,
|
65
|
+
ruby_thread_id: Some(sample.ruby_thread),
|
66
|
+
});
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/// Returns the index of the function in `functions`.
|
71
|
+
/// Calling this method will modify `self.profile` in place.
|
72
|
+
fn function_index_for(&mut self, function: Function) -> FunctionIndex {
|
73
|
+
match self.profile.functions.iter_mut().position(|f| *f == function) {
|
74
|
+
Some(index) => index,
|
75
|
+
None => {
|
76
|
+
self.profile.functions.push(function);
|
77
|
+
self.profile.functions.len() - 1
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
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 {
|
85
|
+
// Build a Location based on (1) the Function and (2) the actual line hit during sampling.
|
86
|
+
let location = Location { function_index, lineno, address: None };
|
87
|
+
match self.profile.locations.iter_mut().position(|l| *l == location) {
|
88
|
+
Some(index) => index,
|
89
|
+
None => {
|
90
|
+
self.profile.locations.push(location);
|
91
|
+
self.profile.locations.len() - 1
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
/// Build a Function from a Ruby frame.
|
97
|
+
fn extract_function_from_ruby_frame(frame: VALUE) -> Function {
|
98
|
+
unsafe {
|
99
|
+
let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame);
|
100
|
+
let frame_full_label: Option<String> = if RTEST(frame_full_label) {
|
101
|
+
Some(
|
102
|
+
CStr::from_ptr(rb_string_value_cstr(&mut frame_full_label))
|
103
|
+
.to_str()
|
104
|
+
.unwrap()
|
105
|
+
.to_owned(),
|
106
|
+
)
|
107
|
+
} else {
|
108
|
+
None
|
109
|
+
};
|
110
|
+
|
111
|
+
let mut frame_path: VALUE = rb_profile_frame_path(frame);
|
112
|
+
let frame_path: Option<String> = if RTEST(frame_path) {
|
113
|
+
Some(
|
114
|
+
CStr::from_ptr(rb_string_value_cstr(&mut frame_path))
|
115
|
+
.to_str()
|
116
|
+
.unwrap()
|
117
|
+
.to_owned(),
|
118
|
+
)
|
119
|
+
} else {
|
120
|
+
None
|
121
|
+
};
|
122
|
+
|
123
|
+
let frame_first_lineno: VALUE = rb_profile_frame_first_lineno(frame);
|
124
|
+
let frame_first_lineno: Option<i32> = if RTEST(frame_first_lineno) {
|
125
|
+
Some(rb_num2int(frame_first_lineno).try_into().unwrap())
|
126
|
+
} else {
|
127
|
+
None
|
128
|
+
};
|
129
|
+
|
130
|
+
let start_address = Self::get_underlying_c_function_address(frame);
|
131
|
+
|
132
|
+
Function {
|
133
|
+
implementation: FunctionImplementation::Ruby,
|
134
|
+
name: frame_full_label,
|
135
|
+
filename: frame_path,
|
136
|
+
start_lineno: frame_first_lineno,
|
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);
|
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
|
323
|
+
}
|
324
|
+
}
|
325
|
+
}
|
@@ -15,7 +15,7 @@ pub const DEFAULT_SCHEDULER: Scheduler = Scheduler::TimerThread;
|
|
15
15
|
#[cfg(not(target_os = "linux"))]
|
16
16
|
pub const DEFAULT_TIME_MODE: TimeMode = TimeMode::WallTime;
|
17
17
|
|
18
|
-
pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(
|
18
|
+
pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(9);
|
19
19
|
|
20
20
|
#[derive(Clone, Debug)]
|
21
21
|
pub struct Configuration {
|
@@ -23,6 +23,7 @@ pub struct Configuration {
|
|
23
23
|
pub interval: Duration,
|
24
24
|
pub time_mode: TimeMode,
|
25
25
|
pub target_ruby_threads: Threads,
|
26
|
+
pub use_experimental_serializer: bool,
|
26
27
|
}
|
27
28
|
|
28
29
|
#[derive(Clone, Debug, PartialEq)]
|
@@ -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
@@ -38,7 +38,7 @@ impl Session {
|
|
38
38
|
unsafe {
|
39
39
|
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
|
40
40
|
};
|
41
|
-
let mut kwargs_values: [VALUE;
|
41
|
+
let mut kwargs_values: [VALUE; 5] = [Qnil.into(); 5];
|
42
42
|
unsafe {
|
43
43
|
rb_get_kwargs(
|
44
44
|
kwargs,
|
@@ -47,10 +47,11 @@ impl Session {
|
|
47
47
|
rb_intern(cstr!("threads")),
|
48
48
|
rb_intern(cstr!("time_mode")),
|
49
49
|
rb_intern(cstr!("scheduler")),
|
50
|
+
rb_intern(cstr!("use_experimental_serializer")),
|
50
51
|
]
|
51
52
|
.as_mut_ptr(),
|
52
53
|
0,
|
53
|
-
|
54
|
+
5,
|
54
55
|
kwargs_values.as_mut_ptr(),
|
55
56
|
);
|
56
57
|
};
|
@@ -59,12 +60,15 @@ impl Session {
|
|
59
60
|
let threads = Self::parse_option_threads(kwargs_values[1]);
|
60
61
|
let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
|
61
62
|
let scheduler = Self::parse_option_scheduler(kwargs_values[3]);
|
63
|
+
let use_experimental_serializer =
|
64
|
+
Self::parse_option_use_experimental_serializer(kwargs_values[4]);
|
62
65
|
|
63
66
|
let configuration = Configuration {
|
64
67
|
scheduler,
|
65
68
|
interval,
|
66
69
|
target_ruby_threads: threads.clone(),
|
67
70
|
time_mode,
|
71
|
+
use_experimental_serializer,
|
68
72
|
};
|
69
73
|
|
70
74
|
match configuration.validate() {
|
@@ -87,10 +91,9 @@ impl Session {
|
|
87
91
|
configuration::Scheduler::Signal => {
|
88
92
|
Arc::new(SignalScheduler::new(&configuration, Arc::clone(&profile)))
|
89
93
|
}
|
90
|
-
configuration::Scheduler::TimerThread =>
|
91
|
-
&configuration,
|
92
|
-
|
93
|
-
)),
|
94
|
+
configuration::Scheduler::TimerThread => {
|
95
|
+
Arc::new(TimerThreadScheduler::new(&configuration, Arc::clone(&profile)))
|
96
|
+
}
|
94
97
|
};
|
95
98
|
|
96
99
|
let running = Arc::new(AtomicBool::new(false));
|
@@ -109,13 +112,7 @@ impl Session {
|
|
109
112
|
configuration::Threads::Targeted(_) => None,
|
110
113
|
};
|
111
114
|
|
112
|
-
Session {
|
113
|
-
configuration,
|
114
|
-
scheduler,
|
115
|
-
profile,
|
116
|
-
running,
|
117
|
-
new_thread_watcher,
|
118
|
-
}
|
115
|
+
Session { configuration, scheduler, profile, running, new_thread_watcher }
|
119
116
|
}
|
120
117
|
|
121
118
|
fn parse_option_interval_ms(value: VALUE) -> Duration {
|
@@ -127,10 +124,10 @@ impl Session {
|
|
127
124
|
let interval_ms = unsafe { rb_num2long(value) };
|
128
125
|
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
|
129
126
|
eprintln!(
|
130
|
-
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (
|
127
|
+
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (9ms).",
|
131
128
|
interval_ms
|
132
129
|
);
|
133
|
-
|
130
|
+
9
|
134
131
|
}))
|
135
132
|
}
|
136
133
|
|
@@ -198,15 +195,19 @@ impl Session {
|
|
198
195
|
// Raise an ArgumentError if the scheduler is not supported on the current platform
|
199
196
|
if !cfg!(target_os = "linux") && scheduler == configuration::Scheduler::Signal {
|
200
197
|
unsafe {
|
201
|
-
rb_raise(
|
202
|
-
rb_eArgError,
|
203
|
-
cstr!("Signal scheduler is not supported on this platform."),
|
204
|
-
)
|
198
|
+
rb_raise(rb_eArgError, cstr!("Signal scheduler is not supported on this platform."))
|
205
199
|
}
|
206
200
|
}
|
207
201
|
scheduler
|
208
202
|
}
|
209
203
|
|
204
|
+
fn parse_option_use_experimental_serializer(value: VALUE) -> bool {
|
205
|
+
if value == Qundef as VALUE {
|
206
|
+
return false;
|
207
|
+
}
|
208
|
+
RTEST(value)
|
209
|
+
}
|
210
|
+
|
210
211
|
pub fn start(&mut self) -> VALUE {
|
211
212
|
self.running.store(true, Ordering::Relaxed);
|
212
213
|
self.start_profile_buffer_flusher_thread();
|
@@ -5,6 +5,7 @@ use crate::profile_serializer::ProfileSerializer;
|
|
5
5
|
use crate::ruby_internal_apis::rb_thread_getcpuclockid;
|
6
6
|
use crate::sample::Sample;
|
7
7
|
use crate::scheduler::Scheduler;
|
8
|
+
use crate::serialization::serializer::ProfileSerializer2;
|
8
9
|
use crate::session::configuration::{self, Configuration};
|
9
10
|
|
10
11
|
use core::panic;
|
@@ -46,6 +47,7 @@ impl Scheduler for SignalScheduler {
|
|
46
47
|
match self.profile.try_write() {
|
47
48
|
Ok(mut profile) => {
|
48
49
|
profile.flush_temporary_sample_buffer();
|
50
|
+
profile.end_instant = Some(std::time::Instant::now());
|
49
51
|
}
|
50
52
|
Err(_) => {
|
51
53
|
println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
|
@@ -56,9 +58,15 @@ impl Scheduler for SignalScheduler {
|
|
56
58
|
let profile = self.profile.try_read().unwrap();
|
57
59
|
log::debug!("Number of samples: {}", profile.samples.len());
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
if self.configuration.use_experimental_serializer {
|
62
|
+
let mut ser = ProfileSerializer2::new();
|
63
|
+
ser.serialize(&profile);
|
64
|
+
ser.to_ruby_hash()
|
65
|
+
} else {
|
66
|
+
let serialized = ProfileSerializer::serialize(&profile);
|
67
|
+
let string = CString::new(serialized).unwrap();
|
68
|
+
unsafe { rb_str_new_cstr(string.as_ptr()) }
|
69
|
+
}
|
62
70
|
}
|
63
71
|
|
64
72
|
fn on_new_thread(&self, thread: VALUE) {
|
@@ -88,10 +96,7 @@ impl Scheduler for SignalScheduler {
|
|
88
96
|
|
89
97
|
impl SignalScheduler {
|
90
98
|
pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
|
91
|
-
Self {
|
92
|
-
configuration: configuration.clone(),
|
93
|
-
profile,
|
94
|
-
}
|
99
|
+
Self { configuration: configuration.clone(), profile }
|
95
100
|
}
|
96
101
|
|
97
102
|
// Install signal handler for profiling events to the current process.
|
@@ -12,6 +12,7 @@ use crate::profile::Profile;
|
|
12
12
|
use crate::profile_serializer::ProfileSerializer;
|
13
13
|
use crate::sample::Sample;
|
14
14
|
use crate::scheduler::Scheduler;
|
15
|
+
use crate::serialization::serializer::ProfileSerializer2;
|
15
16
|
use crate::session::configuration::{self, Configuration};
|
16
17
|
use crate::util::*;
|
17
18
|
|
@@ -61,6 +62,7 @@ impl Scheduler for TimerThreadScheduler {
|
|
61
62
|
match self.profile.try_write() {
|
62
63
|
Ok(mut profile) => {
|
63
64
|
profile.flush_temporary_sample_buffer();
|
65
|
+
profile.end_instant = Some(std::time::Instant::now());
|
64
66
|
}
|
65
67
|
Err(_) => {
|
66
68
|
println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
|
@@ -71,9 +73,15 @@ impl Scheduler for TimerThreadScheduler {
|
|
71
73
|
let profile = self.profile.try_read().unwrap();
|
72
74
|
log::debug!("Number of samples: {}", profile.samples.len());
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
76
|
+
if self.configuration.use_experimental_serializer {
|
77
|
+
let mut ser = ProfileSerializer2::new();
|
78
|
+
ser.serialize(&profile);
|
79
|
+
ser.to_ruby_hash()
|
80
|
+
} else {
|
81
|
+
let serialized = ProfileSerializer::serialize(&profile);
|
82
|
+
let string = CString::new(serialized).unwrap();
|
83
|
+
unsafe { rb_str_new_cstr(string.as_ptr()) }
|
84
|
+
}
|
77
85
|
}
|
78
86
|
|
79
87
|
fn on_new_thread(&self, _thread: VALUE) {
|
data/lib/pf2/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
|
3
5
|
require 'pf2'
|
@@ -55,7 +57,7 @@ module Pf2
|
|
55
57
|
option_parser.parse!(argv)
|
56
58
|
|
57
59
|
profile = JSON.parse(File.read(argv[0]), symbolize_names: true, max_nesting: false)
|
58
|
-
report = JSON.generate(Pf2::Reporter.new(profile).emit)
|
60
|
+
report = JSON.generate(Pf2::Reporter::FirefoxProfiler.new(profile).emit)
|
59
61
|
|
60
62
|
if options[:output_file]
|
61
63
|
File.write(options[:output_file], report)
|