pf2 0.8.0 → 1.0.0.alpha1
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 +11 -0
- data/README.md +20 -4
- data/Rakefile +1 -0
- data/doc/development.md +17 -0
- data/examples/mandelbrot.rb +69 -0
- data/examples/mandelbrot_ractor.rb +77 -0
- data/ext/pf2/backtrace_state.c +10 -0
- data/ext/pf2/backtrace_state.h +10 -0
- data/ext/pf2/configuration.c +90 -0
- data/ext/pf2/configuration.h +23 -0
- data/ext/pf2/debug.h +12 -0
- data/ext/pf2/extconf.rb +23 -6
- data/ext/pf2/pf2.c +17 -0
- data/ext/pf2/pf2.h +8 -0
- data/ext/pf2/ringbuffer.c +74 -0
- data/ext/pf2/ringbuffer.h +24 -0
- data/ext/pf2/sample.c +76 -0
- data/ext/pf2/sample.h +26 -0
- data/ext/pf2/serializer.c +377 -0
- data/ext/pf2/serializer.h +58 -0
- data/ext/pf2/session.c +394 -0
- data/ext/pf2/session.h +56 -0
- data/lib/pf2/cli.rb +25 -11
- data/lib/pf2/reporter/annotate.rb +101 -0
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
- data/lib/pf2/reporter/stack_weaver.rb +8 -0
- data/lib/pf2/reporter.rb +1 -1
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +1 -1
- data/vendor/libbacktrace/.gitignore +5 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/README.md +1 -1
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure +23 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure.ac +10 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/dwarf.c +199 -15
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/elf.c +20 -14
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/fileline.c +2 -2
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/macho.c +2 -2
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/pecoff.c +2 -2
- metadata +111 -111
- data/Cargo.lock +0 -630
- data/Cargo.toml +0 -3
- data/crates/backtrace-sys2/.gitignore +0 -1
- data/crates/backtrace-sys2/Cargo.toml +0 -9
- data/crates/backtrace-sys2/build.rs +0 -45
- data/crates/backtrace-sys2/src/lib.rs +0 -5
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +0 -15
- data/ext/pf2/Cargo.toml +0 -25
- data/ext/pf2/build.rs +0 -10
- data/ext/pf2/src/backtrace.rs +0 -127
- data/ext/pf2/src/lib.rs +0 -22
- data/ext/pf2/src/profile.rs +0 -69
- data/ext/pf2/src/profile_serializer.rs +0 -241
- data/ext/pf2/src/ringbuffer.rs +0 -150
- data/ext/pf2/src/ruby_c_api_helper.c +0 -6
- data/ext/pf2/src/ruby_init.rs +0 -40
- data/ext/pf2/src/ruby_internal_apis.rs +0 -77
- data/ext/pf2/src/sample.rs +0 -67
- data/ext/pf2/src/scheduler.rs +0 -10
- data/ext/pf2/src/serialization/profile.rs +0 -48
- data/ext/pf2/src/serialization/serializer.rs +0 -329
- data/ext/pf2/src/serialization.rs +0 -2
- data/ext/pf2/src/session/configuration.rs +0 -114
- data/ext/pf2/src/session/new_thread_watcher.rs +0 -80
- data/ext/pf2/src/session/ruby_object.rs +0 -90
- data/ext/pf2/src/session.rs +0 -248
- data/ext/pf2/src/siginfo_t.c +0 -5
- data/ext/pf2/src/signal_scheduler.rs +0 -201
- data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +0 -39
- data/ext/pf2/src/timer_thread_scheduler.rs +0 -179
- data/ext/pf2/src/util.rs +0 -31
- data/lib/pf2/reporter/firefox_profiler.rb +0 -397
- data/rust-toolchain.toml +0 -2
- data/rustfmt.toml +0 -1
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Isaac.Newton-Opticks.txt +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/LICENSE +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.am +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/aclocal.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/alloc.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/atomic.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace-supported.h.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/btest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/compile +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/enable.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lead-dot.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/libtool.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltoptions.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltsugar.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltversion.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lt~obsolete.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/multi.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/override.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/unwind_ipinfo.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/warnings.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.guess +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.h.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.sub +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest2.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filenames.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filetype.awk +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-debuginfo-for-buildid.sh.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/instrumented_alloc.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/internal.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ltmain.sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/missing +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmap.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmapio.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/move-if-change +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/nounwind.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/posix.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/print.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/read.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/simple.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/sort.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/state.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/stest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test-driver +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test_format.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ttest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unittest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unknown.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xcoff.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xztest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/zstdtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ztest.c +0 -0
@@ -1,80 +0,0 @@
|
|
1
|
-
use std::collections::HashSet;
|
2
|
-
use std::ffi::c_void;
|
3
|
-
use std::mem::ManuallyDrop;
|
4
|
-
use std::ptr::null_mut;
|
5
|
-
use std::rc::Rc;
|
6
|
-
use std::sync::Mutex;
|
7
|
-
|
8
|
-
use rb_sys::*;
|
9
|
-
|
10
|
-
/// A helper to watch new Ruby threads.
|
11
|
-
///
|
12
|
-
/// `NewThreadWatcher` operates on the Events Hooks API.
|
13
|
-
/// Instead of relying on the `THREAD_EVENT_STARTED` event, it combines the
|
14
|
-
/// `THREAD_EVENT_RESUMED` event and an internal _known-threads_ record.
|
15
|
-
///
|
16
|
-
/// This is to support operations requiring the underlying pthread. Ruby Threads
|
17
|
-
/// are not guaranteed to be fully initialized at the time
|
18
|
-
/// `THREAD_EVENT_STARTED` is triggered; i.e. the underlying pthread has not
|
19
|
-
/// been created yet and `Thread#native_thread_id` returns `nil`.
|
20
|
-
pub struct NewThreadWatcher {
|
21
|
-
inner: Rc<Mutex<Inner>>,
|
22
|
-
event_hook: *mut rb_internal_thread_event_hook_t,
|
23
|
-
}
|
24
|
-
|
25
|
-
struct Inner {
|
26
|
-
known_threads: HashSet<VALUE>,
|
27
|
-
on_new_thread: Box<dyn Fn(VALUE)>,
|
28
|
-
}
|
29
|
-
|
30
|
-
impl NewThreadWatcher {
|
31
|
-
pub fn watch<F>(callback: F) -> Self
|
32
|
-
where
|
33
|
-
F: Fn(VALUE) + 'static,
|
34
|
-
{
|
35
|
-
let mut watcher = Self {
|
36
|
-
inner: Rc::new(Mutex::new(Inner {
|
37
|
-
known_threads: HashSet::new(),
|
38
|
-
on_new_thread: Box::new(callback),
|
39
|
-
})),
|
40
|
-
event_hook: null_mut(),
|
41
|
-
};
|
42
|
-
|
43
|
-
let inner_ptr = Rc::into_raw(Rc::clone(&watcher.inner));
|
44
|
-
unsafe {
|
45
|
-
watcher.event_hook = rb_internal_thread_add_event_hook(
|
46
|
-
Some(Self::on_thread_resume),
|
47
|
-
RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
48
|
-
inner_ptr as *mut c_void,
|
49
|
-
);
|
50
|
-
};
|
51
|
-
|
52
|
-
watcher
|
53
|
-
}
|
54
|
-
|
55
|
-
unsafe extern "C" fn on_thread_resume(
|
56
|
-
_flag: rb_event_flag_t,
|
57
|
-
data: *const rb_internal_thread_event_data_t,
|
58
|
-
custom_data: *mut c_void,
|
59
|
-
) {
|
60
|
-
let ruby_thread: VALUE = unsafe { (*data).thread };
|
61
|
-
|
62
|
-
// A pointer to Box<Inner> is passed as custom_data
|
63
|
-
let inner = unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Inner>)) };
|
64
|
-
let mut inner = inner.lock().unwrap();
|
65
|
-
|
66
|
-
if !inner.known_threads.contains(&ruby_thread) {
|
67
|
-
inner.known_threads.insert(ruby_thread);
|
68
|
-
(inner.on_new_thread)(ruby_thread);
|
69
|
-
}
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
|
-
impl Drop for NewThreadWatcher {
|
74
|
-
fn drop(&mut self) {
|
75
|
-
log::trace!("Cleaning up event hook");
|
76
|
-
unsafe {
|
77
|
-
rb_internal_thread_remove_event_hook(self.event_hook);
|
78
|
-
}
|
79
|
-
}
|
80
|
-
}
|
@@ -1,90 +0,0 @@
|
|
1
|
-
use std::ffi::{c_int, c_void};
|
2
|
-
use std::mem;
|
3
|
-
use std::mem::ManuallyDrop;
|
4
|
-
use std::ptr::{addr_of, null_mut};
|
5
|
-
|
6
|
-
use rb_sys::*;
|
7
|
-
|
8
|
-
use crate::util::cstr;
|
9
|
-
|
10
|
-
use super::Session;
|
11
|
-
|
12
|
-
pub struct SessionRubyObject {
|
13
|
-
session: Option<Session>,
|
14
|
-
}
|
15
|
-
|
16
|
-
impl SessionRubyObject {
|
17
|
-
pub unsafe extern "C" fn rb_initialize(
|
18
|
-
argc: c_int,
|
19
|
-
argv: *const VALUE,
|
20
|
-
rbself: VALUE,
|
21
|
-
) -> VALUE {
|
22
|
-
let mut obj = unsafe { Self::get_struct_from(rbself) };
|
23
|
-
obj.session = Some(Session::new_from_rb_initialize(argc, argv, rbself));
|
24
|
-
Qnil.into()
|
25
|
-
}
|
26
|
-
|
27
|
-
pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
|
28
|
-
let mut obj = Self::get_struct_from(rbself);
|
29
|
-
match &mut obj.session {
|
30
|
-
Some(session) => session.start(),
|
31
|
-
None => panic!("Session is not initialized"),
|
32
|
-
}
|
33
|
-
}
|
34
|
-
|
35
|
-
pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
|
36
|
-
let mut obj = Self::get_struct_from(rbself);
|
37
|
-
match &mut obj.session {
|
38
|
-
Some(session) => session.stop(),
|
39
|
-
None => panic!("Session is not initialized"),
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
// Extract the SessionRubyObject struct from a Ruby object
|
44
|
-
unsafe fn get_struct_from(obj: VALUE) -> ManuallyDrop<Box<Self>> {
|
45
|
-
unsafe {
|
46
|
-
let ptr = rb_check_typeddata(obj, addr_of!(RBDATA));
|
47
|
-
ManuallyDrop::new(Box::from_raw(ptr as *mut SessionRubyObject))
|
48
|
-
}
|
49
|
-
}
|
50
|
-
|
51
|
-
#[allow(non_snake_case)]
|
52
|
-
pub unsafe extern "C" fn rb_alloc(_rbself: VALUE) -> VALUE {
|
53
|
-
let obj = Box::new(SessionRubyObject { session: None });
|
54
|
-
|
55
|
-
let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
|
56
|
-
let rb_cSession = rb_define_class_under(rb_mPf2, cstr!("Session"), rb_cObject);
|
57
|
-
// Wrap the struct into a Ruby object
|
58
|
-
rb_data_typed_object_wrap(rb_cSession, Box::into_raw(obj) as *mut c_void, addr_of!(RBDATA))
|
59
|
-
}
|
60
|
-
|
61
|
-
unsafe extern "C" fn dmark(ptr: *mut c_void) {
|
62
|
-
let obj = ManuallyDrop::new(Box::from_raw(ptr as *mut SessionRubyObject));
|
63
|
-
if let Some(session) = &obj.session {
|
64
|
-
session.dmark()
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
unsafe extern "C" fn dfree(ptr: *mut c_void) {
|
69
|
-
drop(Box::from_raw(ptr as *mut SessionRubyObject));
|
70
|
-
}
|
71
|
-
|
72
|
-
unsafe extern "C" fn dsize(_: *const c_void) -> size_t {
|
73
|
-
// FIXME: Report something better
|
74
|
-
mem::size_of::<SessionRubyObject>() as size_t
|
75
|
-
}
|
76
|
-
}
|
77
|
-
|
78
|
-
static mut RBDATA: rb_data_type_t = rb_data_type_t {
|
79
|
-
wrap_struct_name: cstr!("SessionRubyObject"),
|
80
|
-
function: rb_data_type_struct__bindgen_ty_1 {
|
81
|
-
dmark: Some(SessionRubyObject::dmark),
|
82
|
-
dfree: Some(SessionRubyObject::dfree),
|
83
|
-
dsize: Some(SessionRubyObject::dsize),
|
84
|
-
dcompact: None,
|
85
|
-
reserved: [null_mut(); 1],
|
86
|
-
},
|
87
|
-
parent: null_mut(),
|
88
|
-
data: null_mut(),
|
89
|
-
flags: 0,
|
90
|
-
};
|
data/ext/pf2/src/session.rs
DELETED
@@ -1,248 +0,0 @@
|
|
1
|
-
pub mod configuration;
|
2
|
-
mod new_thread_watcher;
|
3
|
-
pub mod ruby_object;
|
4
|
-
|
5
|
-
use std::collections::HashSet;
|
6
|
-
use std::ffi::{c_int, CStr, CString};
|
7
|
-
use std::str::FromStr as _;
|
8
|
-
use std::sync::atomic::{AtomicBool, Ordering};
|
9
|
-
use std::sync::{Arc, RwLock};
|
10
|
-
use std::thread;
|
11
|
-
use std::time::Duration;
|
12
|
-
|
13
|
-
use rb_sys::*;
|
14
|
-
|
15
|
-
use self::configuration::Configuration;
|
16
|
-
use self::new_thread_watcher::NewThreadWatcher;
|
17
|
-
use crate::profile::Profile;
|
18
|
-
use crate::scheduler::Scheduler;
|
19
|
-
#[cfg(target_os = "linux")]
|
20
|
-
use crate::signal_scheduler::SignalScheduler;
|
21
|
-
#[cfg(not(target_os = "linux"))]
|
22
|
-
use crate::signal_scheduler_unsupported_platform::SignalScheduler;
|
23
|
-
use crate::timer_thread_scheduler::TimerThreadScheduler;
|
24
|
-
use crate::util::*;
|
25
|
-
|
26
|
-
pub struct Session {
|
27
|
-
pub configuration: Configuration,
|
28
|
-
pub scheduler: Arc<dyn Scheduler>,
|
29
|
-
pub profile: Arc<RwLock<Profile>>,
|
30
|
-
pub running: Arc<AtomicBool>,
|
31
|
-
pub new_thread_watcher: Option<NewThreadWatcher>,
|
32
|
-
}
|
33
|
-
|
34
|
-
impl Session {
|
35
|
-
pub fn new_from_rb_initialize(argc: c_int, argv: *const VALUE, rbself: VALUE) -> Self {
|
36
|
-
// Parse arguments
|
37
|
-
let kwargs: VALUE = Qnil.into();
|
38
|
-
unsafe {
|
39
|
-
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
|
40
|
-
};
|
41
|
-
let mut kwargs_values: [VALUE; 5] = [Qnil.into(); 5];
|
42
|
-
unsafe {
|
43
|
-
rb_get_kwargs(
|
44
|
-
kwargs,
|
45
|
-
[
|
46
|
-
rb_intern(cstr!("interval_ms")),
|
47
|
-
rb_intern(cstr!("threads")),
|
48
|
-
rb_intern(cstr!("time_mode")),
|
49
|
-
rb_intern(cstr!("scheduler")),
|
50
|
-
rb_intern(cstr!("use_experimental_serializer")),
|
51
|
-
]
|
52
|
-
.as_mut_ptr(),
|
53
|
-
0,
|
54
|
-
5,
|
55
|
-
kwargs_values.as_mut_ptr(),
|
56
|
-
);
|
57
|
-
};
|
58
|
-
|
59
|
-
let interval = Self::parse_option_interval_ms(kwargs_values[0]);
|
60
|
-
let threads = Self::parse_option_threads(kwargs_values[1]);
|
61
|
-
let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
|
62
|
-
let scheduler = Self::parse_option_scheduler(kwargs_values[3]);
|
63
|
-
let use_experimental_serializer =
|
64
|
-
Self::parse_option_use_experimental_serializer(kwargs_values[4]);
|
65
|
-
|
66
|
-
let configuration = Configuration {
|
67
|
-
scheduler,
|
68
|
-
interval,
|
69
|
-
target_ruby_threads: threads.clone(),
|
70
|
-
time_mode,
|
71
|
-
use_experimental_serializer,
|
72
|
-
};
|
73
|
-
|
74
|
-
match configuration.validate() {
|
75
|
-
Ok(_) => {}
|
76
|
-
Err(msg) => unsafe {
|
77
|
-
rb_raise(rb_eArgError, CString::new(msg).unwrap().as_c_str().as_ptr());
|
78
|
-
},
|
79
|
-
};
|
80
|
-
|
81
|
-
// Store configuration as a Ruby Hash for convenience
|
82
|
-
unsafe {
|
83
|
-
rb_iv_set(rbself, cstr!("@configuration"), configuration.to_rb_hash());
|
84
|
-
}
|
85
|
-
|
86
|
-
// Create a new Profile
|
87
|
-
let profile = Arc::new(RwLock::new(Profile::new()));
|
88
|
-
|
89
|
-
// Initialize the specified Scheduler
|
90
|
-
let scheduler: Arc<dyn Scheduler> = match configuration.scheduler {
|
91
|
-
configuration::Scheduler::Signal => {
|
92
|
-
Arc::new(SignalScheduler::new(&configuration, Arc::clone(&profile)))
|
93
|
-
}
|
94
|
-
configuration::Scheduler::TimerThread => {
|
95
|
-
Arc::new(TimerThreadScheduler::new(&configuration, Arc::clone(&profile)))
|
96
|
-
}
|
97
|
-
};
|
98
|
-
|
99
|
-
let running = Arc::new(AtomicBool::new(false));
|
100
|
-
|
101
|
-
let new_thread_watcher = match threads {
|
102
|
-
configuration::Threads::All => {
|
103
|
-
let scheduler = Arc::clone(&scheduler);
|
104
|
-
let running = Arc::clone(&running);
|
105
|
-
Some(NewThreadWatcher::watch(move |thread: VALUE| {
|
106
|
-
if running.load(Ordering::Relaxed) {
|
107
|
-
log::debug!("New Ruby thread detected: {:?}", thread);
|
108
|
-
scheduler.on_new_thread(thread);
|
109
|
-
}
|
110
|
-
}))
|
111
|
-
}
|
112
|
-
configuration::Threads::Targeted(_) => None,
|
113
|
-
};
|
114
|
-
|
115
|
-
Session { configuration, scheduler, profile, running, new_thread_watcher }
|
116
|
-
}
|
117
|
-
|
118
|
-
fn parse_option_interval_ms(value: VALUE) -> Duration {
|
119
|
-
if value == Qundef as VALUE {
|
120
|
-
// Return default
|
121
|
-
return configuration::DEFAULT_INTERVAL;
|
122
|
-
}
|
123
|
-
|
124
|
-
let interval_ms = unsafe { rb_num2long(value) };
|
125
|
-
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
|
126
|
-
eprintln!(
|
127
|
-
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (9ms).",
|
128
|
-
interval_ms
|
129
|
-
);
|
130
|
-
9
|
131
|
-
}))
|
132
|
-
}
|
133
|
-
|
134
|
-
fn parse_option_threads(value: VALUE) -> configuration::Threads {
|
135
|
-
if (value == Qundef as VALUE)
|
136
|
-
|| (value == Qnil as VALUE)
|
137
|
-
|| (value == unsafe { rb_id2sym(rb_intern(cstr!("all"))) })
|
138
|
-
{
|
139
|
-
return configuration::Threads::All;
|
140
|
-
}
|
141
|
-
|
142
|
-
let mut set: HashSet<VALUE> = HashSet::new();
|
143
|
-
unsafe {
|
144
|
-
for i in 0..RARRAY_LEN(value) {
|
145
|
-
set.insert(rb_ary_entry(value, i));
|
146
|
-
}
|
147
|
-
}
|
148
|
-
configuration::Threads::Targeted(set)
|
149
|
-
}
|
150
|
-
|
151
|
-
fn parse_option_time_mode(value: VALUE) -> configuration::TimeMode {
|
152
|
-
if value == Qundef as VALUE {
|
153
|
-
// Return default
|
154
|
-
return configuration::DEFAULT_TIME_MODE;
|
155
|
-
}
|
156
|
-
|
157
|
-
let specified_mode = unsafe {
|
158
|
-
let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
|
159
|
-
let ptr = rb_string_value_ptr(&mut str);
|
160
|
-
CStr::from_ptr(ptr).to_str().unwrap()
|
161
|
-
};
|
162
|
-
configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
|
163
|
-
// Raise an ArgumentError if the mode is invalid
|
164
|
-
unsafe {
|
165
|
-
rb_raise(
|
166
|
-
rb_eArgError,
|
167
|
-
cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
|
168
|
-
)
|
169
|
-
}
|
170
|
-
})
|
171
|
-
}
|
172
|
-
|
173
|
-
fn parse_option_scheduler(value: VALUE) -> configuration::Scheduler {
|
174
|
-
if value == Qundef as VALUE {
|
175
|
-
// Return default
|
176
|
-
return configuration::DEFAULT_SCHEDULER;
|
177
|
-
}
|
178
|
-
|
179
|
-
let specified_scheduler = unsafe {
|
180
|
-
let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
|
181
|
-
let ptr = rb_string_value_ptr(&mut str);
|
182
|
-
CStr::from_ptr(ptr).to_str().unwrap()
|
183
|
-
};
|
184
|
-
let scheduler =
|
185
|
-
configuration::Scheduler::from_str(specified_scheduler).unwrap_or_else(|_| {
|
186
|
-
// Raise an ArgumentError if the mode is invalid
|
187
|
-
unsafe {
|
188
|
-
rb_raise(
|
189
|
-
rb_eArgError,
|
190
|
-
cstr!("Invalid scheduler. Valid values are ':signal' and ':timer_thread'."),
|
191
|
-
)
|
192
|
-
}
|
193
|
-
});
|
194
|
-
|
195
|
-
// Raise an ArgumentError if the scheduler is not supported on the current platform
|
196
|
-
if !cfg!(target_os = "linux") && scheduler == configuration::Scheduler::Signal {
|
197
|
-
unsafe {
|
198
|
-
rb_raise(rb_eArgError, cstr!("Signal scheduler is not supported on this platform."))
|
199
|
-
}
|
200
|
-
}
|
201
|
-
scheduler
|
202
|
-
}
|
203
|
-
|
204
|
-
fn parse_option_use_experimental_serializer(value: VALUE) -> bool {
|
205
|
-
if value == Qundef as VALUE {
|
206
|
-
return false;
|
207
|
-
}
|
208
|
-
RTEST(value)
|
209
|
-
}
|
210
|
-
|
211
|
-
pub fn start(&mut self) -> VALUE {
|
212
|
-
self.running.store(true, Ordering::Relaxed);
|
213
|
-
self.start_profile_buffer_flusher_thread();
|
214
|
-
self.scheduler.start()
|
215
|
-
}
|
216
|
-
|
217
|
-
fn start_profile_buffer_flusher_thread(&self) {
|
218
|
-
let profile = Arc::clone(&self.profile);
|
219
|
-
let running = Arc::clone(&self.running);
|
220
|
-
log::debug!("flusher: Starting");
|
221
|
-
thread::spawn(move || loop {
|
222
|
-
if !running.load(Ordering::Relaxed) {
|
223
|
-
log::debug!("flusher: Exiting");
|
224
|
-
break;
|
225
|
-
}
|
226
|
-
|
227
|
-
log::trace!("flusher: Flushing temporary sample buffer");
|
228
|
-
match profile.try_write() {
|
229
|
-
Ok(mut profile) => {
|
230
|
-
profile.flush_temporary_sample_buffer();
|
231
|
-
}
|
232
|
-
Err(_) => {
|
233
|
-
log::debug!("flusher: Failed to acquire profile lock");
|
234
|
-
}
|
235
|
-
}
|
236
|
-
thread::sleep(Duration::from_millis(500));
|
237
|
-
});
|
238
|
-
}
|
239
|
-
|
240
|
-
pub fn stop(&mut self) -> VALUE {
|
241
|
-
self.running.store(false, Ordering::Relaxed);
|
242
|
-
self.scheduler.stop()
|
243
|
-
}
|
244
|
-
|
245
|
-
pub fn dmark(&self) {
|
246
|
-
self.scheduler.dmark()
|
247
|
-
}
|
248
|
-
}
|
data/ext/pf2/src/siginfo_t.c
DELETED
@@ -1,201 +0,0 @@
|
|
1
|
-
#![deny(unsafe_op_in_unsafe_fn)]
|
2
|
-
|
3
|
-
use crate::profile::Profile;
|
4
|
-
use crate::profile_serializer::ProfileSerializer;
|
5
|
-
use crate::ruby_internal_apis::rb_thread_getcpuclockid;
|
6
|
-
use crate::sample::Sample;
|
7
|
-
use crate::scheduler::Scheduler;
|
8
|
-
use crate::serialization::serializer::ProfileSerializer2;
|
9
|
-
use crate::session::configuration::{self, Configuration};
|
10
|
-
|
11
|
-
use core::panic;
|
12
|
-
use std::ffi::{c_int, c_void, CString};
|
13
|
-
use std::mem::ManuallyDrop;
|
14
|
-
use std::sync::{Arc, RwLock};
|
15
|
-
use std::{mem, ptr::null_mut};
|
16
|
-
|
17
|
-
use rb_sys::*;
|
18
|
-
|
19
|
-
use crate::util::*;
|
20
|
-
|
21
|
-
#[derive(Debug)]
|
22
|
-
pub struct SignalScheduler {
|
23
|
-
configuration: Configuration,
|
24
|
-
profile: Arc<RwLock<Profile>>,
|
25
|
-
}
|
26
|
-
|
27
|
-
pub struct SignalHandlerArgs {
|
28
|
-
profile: Arc<RwLock<Profile>>,
|
29
|
-
context_ruby_thread: VALUE,
|
30
|
-
}
|
31
|
-
|
32
|
-
impl Scheduler for SignalScheduler {
|
33
|
-
fn start(&self) -> VALUE {
|
34
|
-
self.install_signal_handler();
|
35
|
-
|
36
|
-
if let configuration::Threads::Targeted(threads) = &self.configuration.target_ruby_threads {
|
37
|
-
for ruby_thread in threads.iter() {
|
38
|
-
self.install_timer_to_ruby_thread(*ruby_thread);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
Qtrue.into()
|
43
|
-
}
|
44
|
-
|
45
|
-
fn stop(&self) -> VALUE {
|
46
|
-
// Finalize
|
47
|
-
match self.profile.try_write() {
|
48
|
-
Ok(mut profile) => {
|
49
|
-
profile.flush_temporary_sample_buffer();
|
50
|
-
profile.end_instant = Some(std::time::Instant::now());
|
51
|
-
}
|
52
|
-
Err(_) => {
|
53
|
-
println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
|
54
|
-
return Qfalse.into();
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
let profile = self.profile.try_read().unwrap();
|
59
|
-
log::debug!("Number of samples: {}", profile.samples.len());
|
60
|
-
|
61
|
-
if self.configuration.use_experimental_serializer {
|
62
|
-
let mut ser = ProfileSerializer2::new();
|
63
|
-
ser.serialize(&profile);
|
64
|
-
ser.to_ruby_hash()
|
65
|
-
} else {
|
66
|
-
let serialized = ProfileSerializer::serialize(&profile);
|
67
|
-
let string = CString::new(serialized).unwrap();
|
68
|
-
unsafe { rb_str_new_cstr(string.as_ptr()) }
|
69
|
-
}
|
70
|
-
}
|
71
|
-
|
72
|
-
fn on_new_thread(&self, thread: VALUE) {
|
73
|
-
self.install_timer_to_ruby_thread(thread);
|
74
|
-
}
|
75
|
-
|
76
|
-
fn dmark(&self) {
|
77
|
-
match self.profile.read() {
|
78
|
-
Ok(profile) => unsafe {
|
79
|
-
profile.dmark();
|
80
|
-
},
|
81
|
-
Err(_) => {
|
82
|
-
panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
|
83
|
-
}
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
|
-
fn dfree(&self) {
|
88
|
-
// No-op
|
89
|
-
}
|
90
|
-
|
91
|
-
fn dsize(&self) -> size_t {
|
92
|
-
// FIXME: Report something better
|
93
|
-
mem::size_of::<Self>() as size_t
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
impl SignalScheduler {
|
98
|
-
pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
|
99
|
-
Self { configuration: configuration.clone(), profile }
|
100
|
-
}
|
101
|
-
|
102
|
-
// Install signal handler for profiling events to the current process.
|
103
|
-
fn install_signal_handler(&self) {
|
104
|
-
let mut sa: libc::sigaction = unsafe { mem::zeroed() };
|
105
|
-
sa.sa_sigaction = Self::signal_handler as usize;
|
106
|
-
sa.sa_flags = libc::SA_SIGINFO;
|
107
|
-
let err = unsafe { libc::sigaction(libc::SIGALRM, &sa, null_mut()) };
|
108
|
-
if err != 0 {
|
109
|
-
panic!("sigaction failed: {}", err);
|
110
|
-
}
|
111
|
-
log::debug!("Signal handler installed");
|
112
|
-
}
|
113
|
-
|
114
|
-
// Respond to the signal and collect a sample.
|
115
|
-
// This function is called when a timer fires.
|
116
|
-
//
|
117
|
-
// Expected to be async-signal-safe, but the current implementation is not.
|
118
|
-
extern "C" fn signal_handler(
|
119
|
-
_sig: c_int,
|
120
|
-
info: *mut libc::siginfo_t,
|
121
|
-
_ucontext: *mut libc::ucontext_t,
|
122
|
-
) {
|
123
|
-
let args = unsafe {
|
124
|
-
let ptr = extract_si_value_sival_ptr(info) as *mut SignalHandlerArgs;
|
125
|
-
ManuallyDrop::new(Box::from_raw(ptr))
|
126
|
-
};
|
127
|
-
|
128
|
-
let mut profile = match args.profile.try_write() {
|
129
|
-
Ok(profile) => profile,
|
130
|
-
Err(_) => {
|
131
|
-
// FIXME: Do we want to properly collect GC samples? I don't know yet.
|
132
|
-
log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
|
133
|
-
return;
|
134
|
-
}
|
135
|
-
};
|
136
|
-
|
137
|
-
let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
|
138
|
-
if profile.temporary_sample_buffer.push(sample).is_err() {
|
139
|
-
log::debug!("Temporary sample buffer full. Dropping sample.");
|
140
|
-
}
|
141
|
-
}
|
142
|
-
|
143
|
-
fn install_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
|
144
|
-
// NOTE: This Box never gets dropped
|
145
|
-
let signal_handler_args = Box::new(SignalHandlerArgs {
|
146
|
-
profile: Arc::clone(&self.profile),
|
147
|
-
context_ruby_thread: ruby_thread,
|
148
|
-
});
|
149
|
-
|
150
|
-
// rb_funcall deadlocks when called within a THREAD_EVENT_STARTED hook
|
151
|
-
let kernel_thread_id: i32 = i32::try_from(unsafe {
|
152
|
-
rb_num2int(rb_funcall(
|
153
|
-
ruby_thread,
|
154
|
-
rb_intern(cstr!("native_thread_id")), // kernel thread ID
|
155
|
-
0,
|
156
|
-
))
|
157
|
-
})
|
158
|
-
.unwrap();
|
159
|
-
|
160
|
-
// Create a signal event
|
161
|
-
let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
|
162
|
-
// Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
|
163
|
-
// "trampoline" the signal as any pthread can receive the signal.
|
164
|
-
sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
|
165
|
-
sigevent.sigev_notify_thread_id = kernel_thread_id;
|
166
|
-
sigevent.sigev_signo = libc::SIGALRM;
|
167
|
-
// Pass required args to the signal handler
|
168
|
-
sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
|
169
|
-
|
170
|
-
// Create and configure timer to fire every _interval_ ms of CPU time
|
171
|
-
let mut timer: libc::timer_t = unsafe { mem::zeroed() };
|
172
|
-
let clockid = match self.configuration.time_mode {
|
173
|
-
configuration::TimeMode::CpuTime => unsafe { rb_thread_getcpuclockid(ruby_thread) },
|
174
|
-
configuration::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
|
175
|
-
};
|
176
|
-
let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
|
177
|
-
if err != 0 {
|
178
|
-
panic!("timer_create failed: {}", err);
|
179
|
-
}
|
180
|
-
let itimerspec = Self::duration_to_itimerspec(&self.configuration.interval);
|
181
|
-
let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
|
182
|
-
if err != 0 {
|
183
|
-
panic!("timer_settime failed: {}", err);
|
184
|
-
}
|
185
|
-
|
186
|
-
log::debug!("timer registered for thread {}", ruby_thread);
|
187
|
-
}
|
188
|
-
|
189
|
-
fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
|
190
|
-
let nanos = duration.as_nanos();
|
191
|
-
let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
|
192
|
-
let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
|
193
|
-
|
194
|
-
let mut its: libc::itimerspec = unsafe { mem::zeroed() };
|
195
|
-
its.it_interval.tv_sec = seconds_part;
|
196
|
-
its.it_interval.tv_nsec = nanos_part;
|
197
|
-
its.it_value.tv_sec = seconds_part;
|
198
|
-
its.it_value.tv_nsec = nanos_part;
|
199
|
-
its
|
200
|
-
}
|
201
|
-
}
|
@@ -1,39 +0,0 @@
|
|
1
|
-
use std::sync::{Arc, RwLock};
|
2
|
-
|
3
|
-
use crate::profile::Profile;
|
4
|
-
use crate::scheduler::Scheduler;
|
5
|
-
use crate::session::configuration::Configuration;
|
6
|
-
|
7
|
-
pub struct SignalScheduler {}
|
8
|
-
|
9
|
-
impl Scheduler for SignalScheduler {
|
10
|
-
fn start(&self) -> rb_sys::VALUE {
|
11
|
-
unimplemented!()
|
12
|
-
}
|
13
|
-
|
14
|
-
fn stop(&self) -> rb_sys::VALUE {
|
15
|
-
unimplemented!()
|
16
|
-
}
|
17
|
-
|
18
|
-
fn on_new_thread(&self, thread: rb_sys::VALUE) {
|
19
|
-
unimplemented!()
|
20
|
-
}
|
21
|
-
|
22
|
-
fn dmark(&self) {
|
23
|
-
unimplemented!()
|
24
|
-
}
|
25
|
-
|
26
|
-
fn dfree(&self) {
|
27
|
-
unimplemented!()
|
28
|
-
}
|
29
|
-
|
30
|
-
fn dsize(&self) -> rb_sys::size_t {
|
31
|
-
unimplemented!()
|
32
|
-
}
|
33
|
-
}
|
34
|
-
|
35
|
-
impl SignalScheduler {
|
36
|
-
pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
|
37
|
-
unimplemented!()
|
38
|
-
}
|
39
|
-
}
|