pf2 0.1.0 → 0.3.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -2
  3. data/Cargo.lock +650 -0
  4. data/Cargo.toml +3 -0
  5. data/README.md +110 -13
  6. data/Rakefile +8 -0
  7. data/crates/backtrace-sys2/.gitignore +1 -0
  8. data/crates/backtrace-sys2/Cargo.toml +9 -0
  9. data/crates/backtrace-sys2/build.rs +48 -0
  10. data/crates/backtrace-sys2/src/lib.rs +5 -0
  11. data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
  12. data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
  13. data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
  14. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
  15. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
  16. data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
  17. data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
  18. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
  19. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
  20. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
  21. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
  22. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
  23. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
  24. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
  25. data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
  26. data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
  27. data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
  28. data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
  29. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
  30. data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
  31. data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
  32. data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
  33. data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
  34. data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
  35. data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
  36. data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
  37. data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
  38. data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
  39. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
  40. data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
  41. data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
  42. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
  43. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
  44. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
  45. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
  46. data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
  47. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
  48. data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
  49. data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
  50. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
  51. data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
  52. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
  53. data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
  54. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
  55. data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
  56. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
  57. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
  58. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
  59. data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
  60. data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
  61. data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
  62. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
  63. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
  64. data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
  65. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
  66. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
  67. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
  68. data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
  69. data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
  70. data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
  71. data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
  72. data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
  73. data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
  74. data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
  75. data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
  76. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
  77. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
  78. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
  79. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
  80. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
  81. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
  82. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
  83. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
  84. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
  85. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
  86. data/ext/pf2/Cargo.toml +25 -0
  87. data/ext/pf2/build.rs +3 -0
  88. data/ext/pf2/extconf.rb +6 -1
  89. data/ext/pf2/src/backtrace.rs +126 -0
  90. data/ext/pf2/src/lib.rs +15 -0
  91. data/ext/pf2/src/profile.rs +65 -0
  92. data/ext/pf2/src/profile_serializer.rs +204 -0
  93. data/ext/pf2/src/ringbuffer.rs +152 -0
  94. data/ext/pf2/src/ruby_init.rs +74 -0
  95. data/ext/pf2/src/sample.rs +66 -0
  96. data/ext/pf2/src/siginfo_t.c +5 -0
  97. data/ext/pf2/src/signal_scheduler/configuration.rs +31 -0
  98. data/ext/pf2/src/signal_scheduler/timer_installer.rs +199 -0
  99. data/ext/pf2/src/signal_scheduler.rs +311 -0
  100. data/ext/pf2/src/timer_thread_scheduler.rs +319 -0
  101. data/ext/pf2/src/util.rs +30 -0
  102. data/lib/pf2/cli.rb +1 -1
  103. data/lib/pf2/reporter.rb +48 -16
  104. data/lib/pf2/version.rb +1 -1
  105. data/lib/pf2.rb +20 -5
  106. metadata +128 -5
  107. data/ext/pf2/pf2.c +0 -246
@@ -0,0 +1,319 @@
1
+ #![deny(unsafe_op_in_unsafe_fn)]
2
+
3
+ use std::ffi::{c_int, c_void, CStr, CString};
4
+ use std::mem::ManuallyDrop;
5
+ use std::ptr::null_mut;
6
+ use std::sync::atomic::{AtomicBool, Ordering};
7
+ use std::sync::{Arc, RwLock};
8
+ use std::thread;
9
+ use std::time::Duration;
10
+
11
+ use rb_sys::*;
12
+
13
+ use crate::profile::Profile;
14
+ use crate::profile_serializer::ProfileSerializer;
15
+ use crate::sample::Sample;
16
+ use crate::util::*;
17
+
18
+ #[derive(Clone, Debug)]
19
+ pub struct TimerThreadScheduler {
20
+ ruby_threads: Arc<RwLock<Vec<VALUE>>>,
21
+ interval: Option<Arc<Duration>>,
22
+ profile: Option<Arc<RwLock<Profile>>>,
23
+ stop_requested: Arc<AtomicBool>,
24
+ }
25
+
26
+ #[derive(Debug)]
27
+ struct PostponedJobArgs {
28
+ ruby_threads: Arc<RwLock<Vec<VALUE>>>,
29
+ profile: Arc<RwLock<Profile>>,
30
+ }
31
+
32
+ impl TimerThreadScheduler {
33
+ fn new() -> Self {
34
+ TimerThreadScheduler {
35
+ ruby_threads: Arc::new(RwLock::new(vec![])),
36
+ interval: None,
37
+ profile: None,
38
+ stop_requested: Arc::new(AtomicBool::new(false)),
39
+ }
40
+ }
41
+
42
+ fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
43
+ // Parse arguments
44
+ let kwargs: VALUE = Qnil.into();
45
+ unsafe {
46
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
47
+ };
48
+ let mut kwargs_values: [VALUE; 3] = [Qnil.into(); 3];
49
+ unsafe {
50
+ rb_get_kwargs(
51
+ kwargs,
52
+ [
53
+ rb_intern(cstr!("interval_ms")),
54
+ rb_intern(cstr!("threads")),
55
+ rb_intern(cstr!("time_mode")),
56
+ ]
57
+ .as_mut_ptr(),
58
+ 0,
59
+ 3,
60
+ kwargs_values.as_mut_ptr(),
61
+ );
62
+ };
63
+ let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
64
+ let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
65
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
66
+ eprintln!(
67
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
68
+ interval_ms
69
+ );
70
+ 49
71
+ }))
72
+ } else {
73
+ Duration::from_millis(49)
74
+ };
75
+ let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
76
+ kwargs_values[1]
77
+ } else {
78
+ unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
79
+ };
80
+ if kwargs_values[2] != Qundef as VALUE {
81
+ let specified_mode = unsafe {
82
+ let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
83
+ let ptr = rb_string_value_ptr(&mut str);
84
+ CStr::from_ptr(ptr).to_str().unwrap()
85
+ };
86
+ if specified_mode != "wall" {
87
+ // Raise an ArgumentError
88
+ unsafe {
89
+ rb_raise(
90
+ rb_eArgError,
91
+ cstr!("TimerThreadScheduler only supports :wall mode."),
92
+ )
93
+ }
94
+ }
95
+ }
96
+
97
+ let mut target_ruby_threads = Vec::new();
98
+ unsafe {
99
+ for i in 0..RARRAY_LEN(threads) {
100
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
101
+ target_ruby_threads.push(ruby_thread);
102
+ }
103
+ }
104
+
105
+ self.interval = Some(Arc::new(interval));
106
+ self.ruby_threads = Arc::new(RwLock::new(target_ruby_threads.into_iter().collect()));
107
+
108
+ Qnil.into()
109
+ }
110
+
111
+ fn start(&mut self, _rbself: VALUE) -> VALUE {
112
+ // Create Profile
113
+ let profile = Arc::new(RwLock::new(Profile::new()));
114
+ self.start_profile_buffer_flusher_thread(&profile);
115
+
116
+ // Start monitoring thread
117
+ let stop_requested = Arc::clone(&self.stop_requested);
118
+ let interval = Arc::clone(self.interval.as_ref().unwrap());
119
+ let postponed_job_args: Box<PostponedJobArgs> = Box::new(PostponedJobArgs {
120
+ ruby_threads: Arc::clone(&self.ruby_threads),
121
+ profile: Arc::clone(&profile),
122
+ });
123
+ let postponed_job_handle: rb_postponed_job_handle_t = unsafe {
124
+ rb_postponed_job_preregister(
125
+ 0,
126
+ Some(Self::postponed_job),
127
+ Box::into_raw(postponed_job_args) as *mut c_void, // FIXME: leak
128
+ )
129
+ };
130
+ thread::spawn(move || {
131
+ Self::thread_main_loop(stop_requested, interval, postponed_job_handle)
132
+ });
133
+
134
+ self.profile = Some(profile);
135
+
136
+ Qtrue.into()
137
+ }
138
+
139
+ fn thread_main_loop(
140
+ stop_requested: Arc<AtomicBool>,
141
+ interval: Arc<Duration>,
142
+ postponed_job_handle: rb_postponed_job_handle_t,
143
+ ) {
144
+ loop {
145
+ if stop_requested.fetch_and(true, Ordering::Relaxed) {
146
+ break;
147
+ }
148
+ unsafe {
149
+ rb_postponed_job_trigger(postponed_job_handle);
150
+ }
151
+
152
+ thread::sleep(*interval);
153
+ }
154
+ }
155
+
156
+ fn stop(&self, _rbself: VALUE) -> VALUE {
157
+ // Stop the collector thread
158
+ self.stop_requested.store(true, Ordering::Relaxed);
159
+
160
+ if let Some(profile) = &self.profile {
161
+ // Finalize
162
+ match profile.try_write() {
163
+ Ok(mut profile) => {
164
+ profile.flush_temporary_sample_buffer();
165
+ }
166
+ Err(_) => {
167
+ println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
168
+ return Qfalse.into();
169
+ }
170
+ }
171
+
172
+ let profile = profile.try_read().unwrap();
173
+ log::debug!("Number of samples: {}", profile.samples.len());
174
+
175
+ let serialized = ProfileSerializer::serialize(&profile);
176
+ let serialized = CString::new(serialized).unwrap();
177
+ unsafe { rb_str_new_cstr(serialized.as_ptr()) }
178
+ } else {
179
+ panic!("stop() called before start()");
180
+ }
181
+ }
182
+
183
+ unsafe extern "C" fn postponed_job(ptr: *mut c_void) {
184
+ unsafe {
185
+ rb_gc_disable();
186
+ }
187
+ let args = unsafe { ManuallyDrop::new(Box::from_raw(ptr as *mut PostponedJobArgs)) };
188
+
189
+ let mut profile = match args.profile.try_write() {
190
+ Ok(profile) => profile,
191
+ Err(_) => {
192
+ // FIXME: Do we want to properly collect GC samples? I don't know yet.
193
+ log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
194
+ return;
195
+ }
196
+ };
197
+
198
+ // Collect stack information from specified Ruby Threads
199
+ let ruby_threads = args.ruby_threads.try_read().unwrap();
200
+ for ruby_thread in ruby_threads.iter() {
201
+ // Check if the thread is still alive
202
+ if unsafe { rb_funcall(*ruby_thread, rb_intern(cstr!("status")), 0) } == Qfalse as u64 {
203
+ continue;
204
+ }
205
+
206
+ let sample = Sample::capture(*ruby_thread, &profile.backtrace_state);
207
+ if profile.temporary_sample_buffer.push(sample).is_err() {
208
+ log::debug!("Temporary sample buffer full. Dropping sample.");
209
+ }
210
+ }
211
+ unsafe {
212
+ rb_gc_enable();
213
+ }
214
+ }
215
+
216
+ fn start_profile_buffer_flusher_thread(&self, profile: &Arc<RwLock<Profile>>) {
217
+ let profile = Arc::clone(profile);
218
+ thread::spawn(move || loop {
219
+ log::trace!("Flushing temporary sample buffer");
220
+ match profile.try_write() {
221
+ Ok(mut profile) => {
222
+ profile.flush_temporary_sample_buffer();
223
+ }
224
+ Err(_) => {
225
+ log::debug!("flusher: Failed to acquire profile lock");
226
+ }
227
+ }
228
+ thread::sleep(Duration::from_millis(500));
229
+ });
230
+ }
231
+
232
+ // Ruby Methods
233
+
234
+ pub unsafe extern "C" fn rb_initialize(
235
+ argc: c_int,
236
+ argv: *const VALUE,
237
+ rbself: VALUE,
238
+ ) -> VALUE {
239
+ let mut collector = Self::get_struct_from(rbself);
240
+ collector.initialize(argc, argv, rbself)
241
+ }
242
+
243
+ // SampleCollector.start
244
+ pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
245
+ let mut collector = Self::get_struct_from(rbself);
246
+ collector.start(rbself)
247
+ }
248
+
249
+ // SampleCollector.stop
250
+ pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
251
+ let collector = Self::get_struct_from(rbself);
252
+ collector.stop(rbself)
253
+ }
254
+
255
+ // Functions for TypedData
256
+
257
+ fn get_struct_from(obj: VALUE) -> ManuallyDrop<Box<Self>> {
258
+ unsafe {
259
+ let ptr = rb_check_typeddata(obj, &RBDATA);
260
+ ManuallyDrop::new(Box::from_raw(ptr as *mut TimerThreadScheduler))
261
+ }
262
+ }
263
+
264
+ #[allow(non_snake_case)]
265
+ pub unsafe extern "C" fn rb_alloc(_rbself: VALUE) -> VALUE {
266
+ let collector = TimerThreadScheduler::new();
267
+
268
+ unsafe {
269
+ let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
270
+ let rb_cTimerThreadScheduler =
271
+ rb_define_class_under(rb_mPf2, cstr!("TimerThreadScheduler"), rb_cObject);
272
+
273
+ rb_data_typed_object_wrap(
274
+ rb_cTimerThreadScheduler,
275
+ Box::into_raw(Box::new(collector)) as *mut _ as *mut c_void,
276
+ &RBDATA,
277
+ )
278
+ }
279
+ }
280
+
281
+ unsafe extern "C" fn dmark(ptr: *mut c_void) {
282
+ unsafe {
283
+ let collector = ManuallyDrop::new(Box::from_raw(ptr as *mut TimerThreadScheduler));
284
+ if let Some(profile) = &collector.profile {
285
+ match profile.read() {
286
+ Ok(profile) => {
287
+ profile.dmark();
288
+ }
289
+ Err(_) => {
290
+ panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+ unsafe extern "C" fn dfree(ptr: *mut c_void) {
297
+ unsafe {
298
+ drop(Box::from_raw(ptr as *mut TimerThreadScheduler));
299
+ }
300
+ }
301
+ unsafe extern "C" fn dsize(_: *const c_void) -> size_t {
302
+ // FIXME: Report something better
303
+ std::mem::size_of::<TimerThreadScheduler>() as size_t
304
+ }
305
+ }
306
+
307
+ static mut RBDATA: rb_data_type_t = rb_data_type_t {
308
+ wrap_struct_name: cstr!("TimerThreadScheduler"),
309
+ function: rb_data_type_struct__bindgen_ty_1 {
310
+ dmark: Some(TimerThreadScheduler::dmark),
311
+ dfree: Some(TimerThreadScheduler::dfree),
312
+ dsize: Some(TimerThreadScheduler::dsize),
313
+ dcompact: None,
314
+ reserved: [null_mut(); 1],
315
+ },
316
+ parent: null_mut(),
317
+ data: null_mut(),
318
+ flags: 0,
319
+ };
@@ -0,0 +1,30 @@
1
+ use core::mem::transmute;
2
+ use rb_sys::*;
3
+ use std::ffi::c_void;
4
+
5
+ // Convert str literal to C string literal
6
+ macro_rules! cstr {
7
+ ($s:expr) => {
8
+ concat!($s, "\0").as_ptr() as *const std::ffi::c_char
9
+ };
10
+ }
11
+ pub(crate) use cstr;
12
+
13
+ pub type RubyCFunc = unsafe extern "C" fn() -> VALUE;
14
+
15
+ // TODO: rewrite as macro
16
+ pub fn to_ruby_cfunc_with_no_args<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
17
+ unsafe { transmute::<unsafe extern "C" fn(T) -> VALUE, RubyCFunc>(f) }
18
+ }
19
+ pub fn to_ruby_cfunc_with_args<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
20
+ unsafe { transmute::<unsafe extern "C" fn(T, U, V) -> VALUE, RubyCFunc>(f) }
21
+ }
22
+
23
+ #[allow(non_snake_case)]
24
+ pub fn RTEST(v: VALUE) -> bool {
25
+ v != Qfalse as VALUE && v != Qnil as VALUE
26
+ }
27
+
28
+ extern "C" {
29
+ pub fn extract_si_value_sival_ptr(info: *mut libc::siginfo_t) -> *mut c_void;
30
+ }
data/lib/pf2/cli.rb CHANGED
@@ -27,7 +27,7 @@ module Pf2
27
27
  end
28
28
  option_parser.parse!(argv)
29
29
 
30
- profile = Marshal.load(IO.binread(ARGV[0]))
30
+ profile = JSON.parse(File.read(ARGV[0]), symbolize_names: true, max_nesting: false)
31
31
  report = JSON.generate(Pf2::Reporter.new(profile).emit)
32
32
 
33
33
  if options[:output_file]
data/lib/pf2/reporter.rb CHANGED
@@ -5,7 +5,7 @@ module Pf2
5
5
  # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md
6
6
  class Reporter
7
7
  def initialize(profile)
8
- @profile = profile
8
+ @profile = Reporter.deep_intize_keys(profile)
9
9
  end
10
10
 
11
11
  def inspect
@@ -13,15 +13,15 @@ module Pf2
13
13
  end
14
14
 
15
15
  def emit
16
- x = {
16
+ report = {
17
17
  meta: {
18
18
  interval: 10, # ms; TODO: replace with actual interval
19
19
  start_time: 0,
20
20
  process_type: 0,
21
21
  product: 'ruby',
22
22
  stackwalk: 0,
23
- version: 19,
24
- preprocessed_profile_version: 28,
23
+ version: 28,
24
+ preprocessed_profile_version: 47,
25
25
  symbolicated: true,
26
26
  categories: [
27
27
  {
@@ -34,18 +34,24 @@ module Pf2
34
34
  color: "red",
35
35
  subcategories: ["Code"],
36
36
  },
37
+ {
38
+ name: "Native",
39
+ color: "blue",
40
+ subcategories: ["Code"],
41
+ },
37
42
  {
38
43
  name: "Native",
39
44
  color: "lightblue",
40
45
  subcategories: ["Code"],
41
46
  },
42
47
  ],
48
+ marker_schema: [],
43
49
  },
44
50
  libs: [],
45
51
  counters: [],
46
52
  threads: @profile[:threads].values.map {|th| ThreadReport.new(th).emit }
47
53
  }
48
- Reporter.deep_camelize_keys(x)
54
+ Reporter.deep_camelize_keys(report)
49
55
  end
50
56
 
51
57
  class ThreadReport
@@ -67,7 +73,7 @@ module Pf2
67
73
  def emit
68
74
  func_table = build_func_table
69
75
  frame_table = build_frame_table
70
- stack_table = build_stack_table
76
+ stack_table = build_stack_table(func_table, frame_table)
71
77
  samples = build_samples
72
78
 
73
79
  string_table = build_string_table
@@ -83,7 +89,9 @@ module Pf2
83
89
  name: "Thread (tid: #{@thread[:thread_id]})",
84
90
  is_main_thread: true,
85
91
  is_js_tracer: true,
86
- pid: 1,
92
+ # FIXME: We can fill the correct PID only after we correctly fill is_main_thread
93
+ # (only one thread could be marked as is_main_thread in a single process)
94
+ pid: @thread[:thread_id],
87
95
  tid: @thread[:thread_id],
88
96
  samples: samples,
89
97
  markers: markers,
@@ -114,7 +122,7 @@ module Pf2
114
122
 
115
123
  @thread[:samples].each do |sample|
116
124
  ret[:stack] << @stack_tree_id_map[sample[:stack_tree_id]]
117
- ret[:time] << sample[:timestamp] / 1000000 # ns -> ms
125
+ ret[:time] << sample[:elapsed_ns] / 1000000 # ns -> ms
118
126
  ret[:duration] << 1
119
127
  ret[:event_delay] << 0
120
128
  end
@@ -134,6 +142,8 @@ module Pf2
134
142
  line: [],
135
143
  column: [],
136
144
  optimizations: [],
145
+ inline_depth: [],
146
+ native_symbol: [],
137
147
  }
138
148
 
139
149
  @thread[:frames].each.with_index do |(id, frame), i|
@@ -146,6 +156,8 @@ module Pf2
146
156
  ret[:line] << nil
147
157
  ret[:column] << nil
148
158
  ret[:optimizations] << nil
159
+ ret[:inline_depth] << 0
160
+ ret[:native_symbol] << nil
149
161
 
150
162
  @frame_id_map[id] = i
151
163
  end
@@ -166,8 +178,10 @@ module Pf2
166
178
  }
167
179
 
168
180
  @thread[:frames].each.with_index do |(id, frame), i|
169
- ret[:name] << string_id(frame[:full_label])
170
- ret[:is_js] << false
181
+ native = (frame[:entry_type] == 'Native')
182
+ label = "#{native ? "Native: " : ""}#{frame[:full_label]}"
183
+ ret[:name] << string_id(label)
184
+ ret[:is_js] << !native
171
185
  ret[:relevant_for_js] << false
172
186
  ret[:resource] << -1
173
187
  ret[:file_name] << nil
@@ -181,7 +195,7 @@ module Pf2
181
195
  ret
182
196
  end
183
197
 
184
- def build_stack_table
198
+ def build_stack_table(func_table, frame_table)
185
199
  ret = {
186
200
  frame: [],
187
201
  category: [],
@@ -198,7 +212,7 @@ module Pf2
198
212
 
199
213
  prefix, node = queue.shift
200
214
  ret[:frame] << @frame_id_map[node[:frame_id]]
201
- ret[:category] << 1
215
+ ret[:category] << (build_string_table[func_table[:name][frame_table[:func][@frame_id_map[node[:frame_id]]]]].start_with?('Native:') ? 2 : 1)
202
216
  ret[:subcategory] << nil
203
217
  ret[:prefix] << prefix
204
218
 
@@ -229,6 +243,9 @@ module Pf2
229
243
  data: [],
230
244
  name: [],
231
245
  time: [],
246
+ start_time: [],
247
+ end_time: [],
248
+ phase: [],
232
249
  category: [],
233
250
  length: 0
234
251
  }
@@ -244,17 +261,32 @@ module Pf2
244
261
  s.split('_').inject([]) {|buffer, p| buffer.push(buffer.size == 0 ? p : p.capitalize) }.join
245
262
  end
246
263
 
247
- def deep_camelize_keys(value)
264
+ def deep_transform_keys(value, &block)
248
265
  case value
249
266
  when Array
250
- value.map {|v| deep_camelize_keys(v) }
267
+ value.map {|v| deep_transform_keys(v, &block) }
251
268
  when Hash
252
- Hash[value.map {|k, v| [snake_to_camel(k.to_s).to_sym, deep_camelize_keys(v)] }]
269
+ Hash[value.map {|k, v| [yield(k), deep_transform_keys(v, &block)] }]
253
270
  else
254
271
  value
255
272
  end
256
273
  end
274
+
275
+ def deep_camelize_keys(value)
276
+ deep_transform_keys(value) do |key|
277
+ snake_to_camel(key.to_s).to_sym
278
+ end
279
+ end
280
+
281
+ def deep_intize_keys(value)
282
+ deep_transform_keys(value) do |key|
283
+ if key.to_s.to_i.to_s == key.to_s
284
+ key.to_s.to_i
285
+ else
286
+ key
287
+ end
288
+ end
289
+ end
257
290
  end
258
291
  end
259
292
  end
260
-
data/lib/pf2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pf2
2
- VERSION = '0.1.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/pf2.rb CHANGED
@@ -4,13 +4,28 @@ require_relative 'pf2/version'
4
4
  module Pf2
5
5
  class Error < StandardError; end
6
6
 
7
- @@threads = []
7
+ def self.default_scheduler_class
8
+ # SignalScheduler is Linux-only. Use TimerThreadScheduler on other platforms.
9
+ if defined?(SignalScheduler)
10
+ SignalScheduler
11
+ else
12
+ TimerThreadScheduler
13
+ end
14
+ end
15
+
16
+ def self.start(...)
17
+ @@default_scheduler = default_scheduler_class.new(...)
18
+ @@default_scheduler.start
19
+ end
8
20
 
9
- def self.threads
10
- @@threads
21
+ def self.stop(...)
22
+ @@default_scheduler.stop(...)
11
23
  end
12
24
 
13
- def self.threads=(th)
14
- @@threads = th
25
+ def self.profile(&block)
26
+ raise ArgumentError, "block required" unless block_given?
27
+ start([Thread.current], true)
28
+ yield
29
+ stop
15
30
  end
16
31
  end