pf2 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -2
- data/Cargo.lock +650 -0
- data/Cargo.toml +3 -0
- data/README.md +110 -13
- data/Rakefile +8 -0
- data/crates/backtrace-sys2/.gitignore +1 -0
- data/crates/backtrace-sys2/Cargo.toml +9 -0
- data/crates/backtrace-sys2/build.rs +48 -0
- data/crates/backtrace-sys2/src/lib.rs +5 -0
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
- data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
- data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
- data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
- data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
- data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
- data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
- data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
- data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
- data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
- data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
- data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
- data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
- data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
- data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
- data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
- data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
- data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
- data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
- data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
- data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
- data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
- data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
- data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
- data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
- data/ext/pf2/Cargo.toml +25 -0
- data/ext/pf2/build.rs +3 -0
- data/ext/pf2/extconf.rb +6 -1
- data/ext/pf2/src/backtrace.rs +126 -0
- data/ext/pf2/src/lib.rs +15 -0
- data/ext/pf2/src/profile.rs +65 -0
- data/ext/pf2/src/profile_serializer.rs +204 -0
- data/ext/pf2/src/ringbuffer.rs +152 -0
- data/ext/pf2/src/ruby_init.rs +74 -0
- data/ext/pf2/src/sample.rs +66 -0
- data/ext/pf2/src/siginfo_t.c +5 -0
- data/ext/pf2/src/signal_scheduler/configuration.rs +31 -0
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +199 -0
- data/ext/pf2/src/signal_scheduler.rs +311 -0
- data/ext/pf2/src/timer_thread_scheduler.rs +319 -0
- data/ext/pf2/src/util.rs +30 -0
- data/lib/pf2/cli.rb +1 -1
- data/lib/pf2/reporter.rb +48 -16
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +20 -5
- metadata +128 -5
- 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
|
+
};
|
data/ext/pf2/src/util.rs
ADDED
@@ -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 =
|
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
|
-
|
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:
|
24
|
-
preprocessed_profile_version:
|
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(
|
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
|
-
|
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[:
|
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
|
-
|
170
|
-
|
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
|
264
|
+
def deep_transform_keys(value, &block)
|
248
265
|
case value
|
249
266
|
when Array
|
250
|
-
value.map {|v|
|
267
|
+
value.map {|v| deep_transform_keys(v, &block) }
|
251
268
|
when Hash
|
252
|
-
Hash[value.map {|k, 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
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
|
-
|
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.
|
10
|
-
@@
|
21
|
+
def self.stop(...)
|
22
|
+
@@default_scheduler.stop(...)
|
11
23
|
end
|
12
24
|
|
13
|
-
def self.
|
14
|
-
|
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
|