pf2 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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: []