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.
- 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
|