pf2 0.5.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Cargo.lock +7 -27
  4. data/README.md +1 -1
  5. data/crates/backtrace-sys2/build.rs +1 -4
  6. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +116 -31
  7. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +295 -141
  8. data/crates/backtrace-sys2/src/libbacktrace/README.md +11 -1
  9. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +1 -1
  10. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +1 -1
  11. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +1 -1
  12. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +1 -1
  13. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +1 -1
  14. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +1 -1
  15. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +12 -12
  16. data/crates/backtrace-sys2/src/libbacktrace/btest.c +24 -8
  17. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +162 -53
  18. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +3 -0
  19. data/crates/backtrace-sys2/src/libbacktrace/configure +255 -66
  20. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +27 -8
  21. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +37 -30
  22. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +2 -2
  23. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +1 -1
  24. data/crates/backtrace-sys2/src/libbacktrace/elf.c +98 -76
  25. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +1 -1
  26. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +2 -2
  27. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +1 -1
  28. data/crates/backtrace-sys2/src/libbacktrace/internal.h +41 -2
  29. data/crates/backtrace-sys2/src/libbacktrace/macho.c +25 -19
  30. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +1 -1
  31. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +1 -1
  32. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +4 -4
  33. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +1 -1
  34. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +192 -26
  35. data/crates/backtrace-sys2/src/libbacktrace/posix.c +1 -1
  36. data/crates/backtrace-sys2/src/libbacktrace/print.c +41 -16
  37. data/crates/backtrace-sys2/src/libbacktrace/read.c +1 -1
  38. data/crates/backtrace-sys2/src/libbacktrace/simple.c +1 -1
  39. data/crates/backtrace-sys2/src/libbacktrace/sort.c +1 -1
  40. data/crates/backtrace-sys2/src/libbacktrace/state.c +1 -1
  41. data/crates/backtrace-sys2/src/libbacktrace/stest.c +1 -1
  42. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +1 -1
  43. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +1 -1
  44. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +1 -1
  45. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +1 -1
  46. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +1 -1
  47. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +1 -1
  48. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +43 -32
  49. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +2 -2
  50. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +1 -1
  51. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +1 -1
  52. data/ext/pf2/Cargo.toml +1 -1
  53. data/ext/pf2/src/lib.rs +1 -0
  54. data/ext/pf2/src/profile.rs +7 -3
  55. data/ext/pf2/src/profile_serializer.rs +6 -13
  56. data/ext/pf2/src/ringbuffer.rs +1 -3
  57. data/ext/pf2/src/ruby_init.rs +1 -4
  58. data/ext/pf2/src/sample.rs +1 -0
  59. data/ext/pf2/src/serialization/profile.rs +47 -0
  60. data/ext/pf2/src/serialization/serializer.rs +325 -0
  61. data/ext/pf2/src/serialization.rs +2 -0
  62. data/ext/pf2/src/session/configuration.rs +2 -1
  63. data/ext/pf2/src/session/new_thread_watcher.rs +1 -1
  64. data/ext/pf2/src/session/ruby_object.rs +1 -5
  65. data/ext/pf2/src/session.rs +20 -19
  66. data/ext/pf2/src/signal_scheduler.rs +12 -7
  67. data/ext/pf2/src/timer_thread_scheduler.rs +11 -3
  68. data/lib/pf2/cli.rb +3 -1
  69. data/lib/pf2/reporter/firefox_profiler.rb +397 -0
  70. data/lib/pf2/reporter/stack_weaver.rb +81 -0
  71. data/lib/pf2/reporter.rb +3 -392
  72. data/lib/pf2/serve.rb +3 -1
  73. data/lib/pf2/session.rb +2 -0
  74. data/lib/pf2/version.rb +3 -1
  75. data/lib/pf2.rb +4 -1
  76. data/rustfmt.toml +1 -0
  77. metadata +13 -12
  78. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +0 -7436
  79. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +0 -369
  80. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +0 -123
  81. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +0 -23
  82. 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
+ }
@@ -0,0 +1,2 @@
1
+ pub mod profile;
2
+ pub mod serializer;
@@ -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(49);
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 rb_internal_thread_event_data,
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) {
@@ -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; 4] = [Qnil.into(); 4];
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
- 4,
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 => Arc::new(TimerThreadScheduler::new(
91
- &configuration,
92
- Arc::clone(&profile),
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 (49ms).",
127
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (9ms).",
131
128
  interval_ms
132
129
  );
133
- 49
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
- let serialized = ProfileSerializer::serialize(&profile);
60
- let serialized = CString::new(serialized).unwrap();
61
- unsafe { rb_str_new_cstr(serialized.as_ptr()) }
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
- let serialized = ProfileSerializer::serialize(&profile);
75
- let serialized = CString::new(serialized).unwrap();
76
- unsafe { rb_str_new_cstr(serialized.as_ptr()) }
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)