pf2 0.1.0 → 0.3.0

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