ext/pf2/src/signal_scheduler.rs in pf2-0.2.0 vs ext/pf2/src/signal_scheduler.rs in pf2-0.3.0
- old
+ new
@@ -9,12 +9,13 @@
use crate::profile_serializer::ProfileSerializer;
use crate::sample::Sample;
use core::panic;
use std::collections::HashSet;
-use std::ffi::{c_int, c_void, CString};
+use std::ffi::{c_int, c_void, CStr, CString};
use std::mem::ManuallyDrop;
+use std::str::FromStr;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;
use std::{mem, ptr::null_mut};
@@ -22,11 +23,11 @@
use crate::util::*;
#[derive(Debug)]
pub struct SignalScheduler {
- configuration: configuration::Configuration,
+ configuration: Option<configuration::Configuration>,
profile: Option<Arc<RwLock<Profile>>>,
}
pub struct SignalHandlerArgs {
profile: Arc<RwLock<Profile>>,
@@ -34,41 +35,104 @@
}
impl SignalScheduler {
fn new() -> Self {
Self {
- configuration: Configuration {
- time_mode: TimeMode::CpuTime,
- },
+ configuration: None,
profile: None,
}
}
- fn start(
- &mut self,
- _rbself: VALUE,
- ruby_threads_rary: VALUE,
- track_new_threads: VALUE,
- ) -> VALUE {
- let track_new_threads = RTEST(track_new_threads);
+ fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
+ // Parse arguments
+ let kwargs: VALUE = Qnil.into();
+ unsafe {
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
+ };
+ let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
+ unsafe {
+ rb_get_kwargs(
+ kwargs,
+ [
+ rb_intern(cstr!("interval_ms")),
+ rb_intern(cstr!("threads")),
+ rb_intern(cstr!("time_mode")),
+ rb_intern(cstr!("track_new_threads")),
+ ]
+ .as_mut_ptr(),
+ 0,
+ 4,
+ kwargs_values.as_mut_ptr(),
+ );
+ };
+ let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
+ let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
+ eprintln!(
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
+ interval_ms
+ );
+ 49
+ }))
+ } else {
+ Duration::from_millis(49)
+ };
+ let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
+ kwargs_values[1]
+ } else {
+ unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
+ };
+ let time_mode: configuration::TimeMode = if kwargs_values[2] != Qundef as VALUE {
+ let specified_mode = unsafe {
+ let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
+ let ptr = rb_string_value_ptr(&mut str);
+ CStr::from_ptr(ptr).to_str().unwrap()
+ };
+ configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
+ // Raise an ArgumentError
+ unsafe {
+ rb_raise(
+ rb_eArgError,
+ cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
+ )
+ }
+ })
+ } else {
+ configuration::TimeMode::CpuTime
+ };
+ let track_new_threads: bool = if kwargs_values[3] != Qundef as VALUE {
+ RTEST(kwargs_values[3])
+ } else {
+ false
+ };
- let profile = Arc::new(RwLock::new(Profile::new()));
- self.start_profile_buffer_flusher_thread(&profile);
- self.install_signal_handler();
-
let mut target_ruby_threads = HashSet::new();
unsafe {
- for i in 0..RARRAY_LEN(ruby_threads_rary) {
- let ruby_thread: VALUE = rb_ary_entry(ruby_threads_rary, i);
+ for i in 0..RARRAY_LEN(threads) {
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
target_ruby_threads.insert(ruby_thread);
}
}
+
+ self.configuration = Some(Configuration {
+ interval,
+ target_ruby_threads,
+ time_mode,
+ track_new_threads,
+ });
+
+ Qnil.into()
+ }
+
+ fn start(&mut self, _rbself: VALUE) -> VALUE {
+ let profile = Arc::new(RwLock::new(Profile::new()));
+ self.start_profile_buffer_flusher_thread(&profile);
+ self.install_signal_handler();
+
TimerInstaller::install_timer_to_ruby_threads(
- self.configuration.clone(),
- &target_ruby_threads,
+ self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
Arc::clone(&profile),
- track_new_threads,
);
self.profile = Some(profile);
Qtrue.into()
@@ -130,11 +194,11 @@
log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
return;
}
};
- let sample = Sample::capture(args.context_ruby_thread); // NOT async-signal-safe
+ let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
if profile.temporary_sample_buffer.push(sample).is_err() {
log::debug!("Temporary sample buffer full. Dropping sample.");
}
}
@@ -154,16 +218,21 @@
});
}
// Ruby Methods
- pub unsafe extern "C" fn rb_start(
+ pub unsafe extern "C" fn rb_initialize(
+ argc: c_int,
+ argv: *const VALUE,
rbself: VALUE,
- ruby_threads: VALUE,
- track_new_threads: VALUE,
) -> VALUE {
let mut collector = unsafe { Self::get_struct_from(rbself) };
- collector.start(rbself, ruby_threads, track_new_threads)
+ collector.initialize(argc, argv, rbself)
+ }
+
+ pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
+ let mut collector = unsafe { Self::get_struct_from(rbself) };
+ collector.start(rbself)
}
pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
let mut collector = unsafe { Self::get_struct_from(rbself) };
collector.stop(rbself)