pf2 0.5.2 → 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 +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)
|