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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Cargo.lock +7 -27
  4. data/crates/backtrace-sys2/build.rs +1 -4
  5. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +116 -31
  6. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +295 -141
  7. data/crates/backtrace-sys2/src/libbacktrace/README.md +11 -1
  8. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +1 -1
  9. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +1 -1
  10. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +1 -1
  11. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +1 -1
  12. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +1 -1
  13. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +1 -1
  14. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +12 -12
  15. data/crates/backtrace-sys2/src/libbacktrace/btest.c +24 -8
  16. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +162 -53
  17. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +3 -0
  18. data/crates/backtrace-sys2/src/libbacktrace/configure +255 -66
  19. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +27 -8
  20. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +37 -30
  21. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +2 -2
  22. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +1 -1
  23. data/crates/backtrace-sys2/src/libbacktrace/elf.c +98 -76
  24. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +1 -1
  25. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +2 -2
  26. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +1 -1
  27. data/crates/backtrace-sys2/src/libbacktrace/internal.h +41 -2
  28. data/crates/backtrace-sys2/src/libbacktrace/macho.c +25 -19
  29. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +1 -1
  30. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +1 -1
  31. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +4 -4
  32. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +1 -1
  33. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +192 -26
  34. data/crates/backtrace-sys2/src/libbacktrace/posix.c +1 -1
  35. data/crates/backtrace-sys2/src/libbacktrace/print.c +41 -16
  36. data/crates/backtrace-sys2/src/libbacktrace/read.c +1 -1
  37. data/crates/backtrace-sys2/src/libbacktrace/simple.c +1 -1
  38. data/crates/backtrace-sys2/src/libbacktrace/sort.c +1 -1
  39. data/crates/backtrace-sys2/src/libbacktrace/state.c +1 -1
  40. data/crates/backtrace-sys2/src/libbacktrace/stest.c +1 -1
  41. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +1 -1
  42. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +1 -1
  43. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +1 -1
  44. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +1 -1
  45. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +1 -1
  46. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +1 -1
  47. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +43 -32
  48. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +2 -2
  49. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +1 -1
  50. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +1 -1
  51. data/ext/pf2/Cargo.toml +1 -1
  52. data/ext/pf2/src/profile_serializer.rs +5 -12
  53. data/ext/pf2/src/ringbuffer.rs +1 -3
  54. data/ext/pf2/src/ruby_init.rs +1 -4
  55. data/ext/pf2/src/sample.rs +1 -0
  56. data/ext/pf2/src/serialization/profile.rs +2 -1
  57. data/ext/pf2/src/serialization/serializer.rs +227 -48
  58. data/ext/pf2/src/session/new_thread_watcher.rs +1 -1
  59. data/ext/pf2/src/session/ruby_object.rs +1 -5
  60. data/ext/pf2/src/session.rs +5 -15
  61. data/ext/pf2/src/signal_scheduler.rs +9 -10
  62. data/ext/pf2/src/timer_thread_scheduler.rs +8 -6
  63. data/lib/pf2/cli.rb +2 -0
  64. data/lib/pf2/reporter/firefox_profiler.rb +2 -0
  65. data/lib/pf2/reporter/stack_weaver.rb +81 -0
  66. data/lib/pf2/reporter.rb +3 -4
  67. data/lib/pf2/serve.rb +2 -0
  68. data/lib/pf2/session.rb +2 -0
  69. data/lib/pf2/version.rb +3 -1
  70. data/lib/pf2.rb +4 -1
  71. data/rustfmt.toml +1 -0
  72. metadata +9 -12
  73. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +0 -7436
  74. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +0 -369
  75. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +0 -123
  76. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +0 -23
  77. 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::{Function, FunctionImplementation, Location, LocationIndex, Profile, Sample};
6
- use crate::util::RTEST;
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) -> String {
28
+ pub fn serialize(&mut self, source: &crate::profile::Profile) {
26
29
  // Fill in meta fields
27
- self.profile.start_timestamp_ns = source
28
- .start_timestamp
29
- .duration_since(std::time::UNIX_EPOCH)
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
- // Get the Location corresponding to the frame.
49
- let location_index = self.process_ruby_frame(frame, lineno);
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
- /// Process a collected Ruby frame.
70
+ /// Returns the index of the function in `functions`.
64
71
  /// Calling this method will modify `self.profile` in place.
65
- ///
66
- /// Returns the index of the location in `locations`.
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
- function_index,
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
- fn extract_function_from_frame(frame: VALUE) -> Function {
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: None,
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 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) {
@@ -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 => Arc::new(TimerThreadScheduler::new(
95
- &configuration,
96
- Arc::clone(&profile),
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
- let serialized = if self.configuration.use_experimental_serializer {
62
- ProfileSerializer2::new().serialize(&profile)
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
- let serialized = CString::new(serialized).unwrap();
67
- unsafe { rb_str_new_cstr(serialized.as_ptr()) }
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
- let serialized = if self.configuration.use_experimental_serializer {
77
- ProfileSerializer2::new().serialize(&profile)
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
- let serialized = CString::new(serialized).unwrap();
82
- unsafe { rb_str_new_cstr(serialized.as_ptr()) }
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  require 'pf2'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
 
3
5
  module Pf2
@@ -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
@@ -1,5 +1,4 @@
1
- require_relative './reporter/firefox_profiler'
1
+ # frozen_string_literal: true
2
2
 
3
- module Pf2
4
- module Reporter; end
5
- end
3
+ require_relative './reporter/stack_weaver'
4
+ require_relative './reporter/firefox_profiler'
data/lib/pf2/serve.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'logger'
3
5
  require 'uri'
data/lib/pf2/session.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pf2
2
4
  class Session
3
5
  attr_reader :configuration
data/lib/pf2/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pf2
2
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
3
5
  end
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.6.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: 2024-07-14 00:00:00.000000000 Z
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.63
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.63
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.0.dev
212
+ rubygems_version: 3.6.2
216
213
  specification_version: 4
217
214
  summary: Yet another Ruby profiler
218
215
  test_files: []