pf2 0.3.0 → 0.5.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.
@@ -0,0 +1,90 @@
1
+ use std::ffi::{c_int, c_void};
2
+ use std::mem;
3
+ use std::mem::ManuallyDrop;
4
+ use std::ptr::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, &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, &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
+ };
@@ -0,0 +1,227 @@
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
+ use crate::signal_scheduler::SignalScheduler;
20
+ use crate::timer_thread_scheduler::TimerThreadScheduler;
21
+ use crate::util::*;
22
+
23
+ pub struct Session {
24
+ pub configuration: Configuration,
25
+ pub scheduler: Arc<dyn Scheduler>,
26
+ pub profile: Arc<RwLock<Profile>>,
27
+ pub running: Arc<AtomicBool>,
28
+ pub new_thread_watcher: Option<NewThreadWatcher>,
29
+ }
30
+
31
+ impl Session {
32
+ pub fn new_from_rb_initialize(argc: c_int, argv: *const VALUE, rbself: VALUE) -> Self {
33
+ // Parse arguments
34
+ let kwargs: VALUE = Qnil.into();
35
+ unsafe {
36
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
37
+ };
38
+ let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
39
+ unsafe {
40
+ rb_get_kwargs(
41
+ kwargs,
42
+ [
43
+ rb_intern(cstr!("interval_ms")),
44
+ rb_intern(cstr!("threads")),
45
+ rb_intern(cstr!("time_mode")),
46
+ rb_intern(cstr!("scheduler")),
47
+ ]
48
+ .as_mut_ptr(),
49
+ 0,
50
+ 4,
51
+ kwargs_values.as_mut_ptr(),
52
+ );
53
+ };
54
+
55
+ let interval = Self::parse_option_interval_ms(kwargs_values[0]);
56
+ let threads = Self::parse_option_threads(kwargs_values[1]);
57
+ let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
58
+ let scheduler = Self::parse_option_scheduler(kwargs_values[3]);
59
+
60
+ let configuration = Configuration {
61
+ scheduler,
62
+ interval,
63
+ target_ruby_threads: threads.clone(),
64
+ time_mode,
65
+ };
66
+
67
+ match configuration.validate() {
68
+ Ok(_) => {}
69
+ Err(msg) => unsafe {
70
+ rb_raise(rb_eArgError, CString::new(msg).unwrap().as_c_str().as_ptr());
71
+ },
72
+ };
73
+
74
+ // Store configuration as a Ruby Hash for convenience
75
+ unsafe {
76
+ rb_iv_set(rbself, cstr!("@configuration"), configuration.to_rb_hash());
77
+ }
78
+
79
+ // Create a new Profile
80
+ let profile = Arc::new(RwLock::new(Profile::new()));
81
+
82
+ // Initialize the specified Scheduler
83
+ let scheduler: Arc<dyn Scheduler> = match configuration.scheduler {
84
+ configuration::Scheduler::Signal => {
85
+ Arc::new(SignalScheduler::new(&configuration, Arc::clone(&profile)))
86
+ }
87
+ configuration::Scheduler::TimerThread => Arc::new(TimerThreadScheduler::new(
88
+ &configuration,
89
+ Arc::clone(&profile),
90
+ )),
91
+ };
92
+
93
+ let new_thread_watcher = match threads {
94
+ configuration::Threads::All => {
95
+ let scheduler = Arc::clone(&scheduler);
96
+ Some(NewThreadWatcher::watch(move |thread: VALUE| {
97
+ log::debug!("New Ruby thread detected: {:?}", thread);
98
+ scheduler.on_new_thread(thread);
99
+ }))
100
+ }
101
+ configuration::Threads::Targeted(_) => None,
102
+ };
103
+
104
+ Session {
105
+ configuration,
106
+ scheduler,
107
+ profile,
108
+ running: Arc::new(AtomicBool::new(false)),
109
+ new_thread_watcher,
110
+ }
111
+ }
112
+
113
+ fn parse_option_interval_ms(value: VALUE) -> Duration {
114
+ if value == Qundef as VALUE {
115
+ // Return default
116
+ return configuration::DEFAULT_INTERVAL;
117
+ }
118
+
119
+ let interval_ms = unsafe { rb_num2long(value) };
120
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
121
+ eprintln!(
122
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
123
+ interval_ms
124
+ );
125
+ 49
126
+ }))
127
+ }
128
+
129
+ fn parse_option_threads(value: VALUE) -> configuration::Threads {
130
+ if (value == Qundef as VALUE)
131
+ || (value == Qnil as VALUE)
132
+ || (value == unsafe { rb_id2sym(rb_intern(cstr!("all"))) })
133
+ {
134
+ return configuration::Threads::All;
135
+ }
136
+
137
+ let mut set: HashSet<VALUE> = HashSet::new();
138
+ unsafe {
139
+ for i in 0..RARRAY_LEN(value) {
140
+ set.insert(rb_ary_entry(value, i));
141
+ }
142
+ }
143
+ configuration::Threads::Targeted(set)
144
+ }
145
+
146
+ fn parse_option_time_mode(value: VALUE) -> configuration::TimeMode {
147
+ if value == Qundef as VALUE {
148
+ // Return default
149
+ return configuration::DEFAULT_TIME_MODE;
150
+ }
151
+
152
+ let specified_mode = unsafe {
153
+ let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
154
+ let ptr = rb_string_value_ptr(&mut str);
155
+ CStr::from_ptr(ptr).to_str().unwrap()
156
+ };
157
+ configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
158
+ // Raise an ArgumentError if the mode is invalid
159
+ unsafe {
160
+ rb_raise(
161
+ rb_eArgError,
162
+ cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
163
+ )
164
+ }
165
+ })
166
+ }
167
+
168
+ fn parse_option_scheduler(value: VALUE) -> configuration::Scheduler {
169
+ if value == Qundef as VALUE {
170
+ // Return default
171
+ return configuration::DEFAULT_SCHEDULER;
172
+ }
173
+
174
+ let specified_scheduler = unsafe {
175
+ let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
176
+ let ptr = rb_string_value_ptr(&mut str);
177
+ CStr::from_ptr(ptr).to_str().unwrap()
178
+ };
179
+ configuration::Scheduler::from_str(specified_scheduler).unwrap_or_else(|_| {
180
+ // Raise an ArgumentError if the mode is invalid
181
+ unsafe {
182
+ rb_raise(
183
+ rb_eArgError,
184
+ cstr!("Invalid scheduler. Valid values are ':signal' and ':timer_thread'."),
185
+ )
186
+ }
187
+ })
188
+ }
189
+
190
+ pub fn start(&mut self) -> VALUE {
191
+ self.running.store(true, Ordering::Relaxed);
192
+ self.start_profile_buffer_flusher_thread();
193
+ self.scheduler.start()
194
+ }
195
+
196
+ fn start_profile_buffer_flusher_thread(&self) {
197
+ let profile = Arc::clone(&self.profile);
198
+ let running = Arc::clone(&self.running);
199
+ log::debug!("flusher: Starting");
200
+ thread::spawn(move || loop {
201
+ if !running.load(Ordering::Relaxed) {
202
+ log::debug!("flusher: Exiting");
203
+ break;
204
+ }
205
+
206
+ log::trace!("flusher: Flushing temporary sample buffer");
207
+ match profile.try_write() {
208
+ Ok(mut profile) => {
209
+ profile.flush_temporary_sample_buffer();
210
+ }
211
+ Err(_) => {
212
+ log::debug!("flusher: Failed to acquire profile lock");
213
+ }
214
+ }
215
+ thread::sleep(Duration::from_millis(500));
216
+ });
217
+ }
218
+
219
+ pub fn stop(&mut self) -> VALUE {
220
+ self.running.store(false, Ordering::Relaxed);
221
+ self.scheduler.stop()
222
+ }
223
+
224
+ pub fn dmark(&self) {
225
+ self.scheduler.dmark()
226
+ }
227
+ }
@@ -1,22 +1,16 @@
1
1
  #![deny(unsafe_op_in_unsafe_fn)]
2
2
 
3
- mod configuration;
4
- mod timer_installer;
5
-
6
- use self::configuration::{Configuration, TimeMode};
7
- use self::timer_installer::TimerInstaller;
8
3
  use crate::profile::Profile;
9
4
  use crate::profile_serializer::ProfileSerializer;
5
+ use crate::ruby_internal_apis::rb_thread_getcpuclockid;
10
6
  use crate::sample::Sample;
7
+ use crate::scheduler::Scheduler;
8
+ use crate::session::configuration::{self, Configuration};
11
9
 
12
10
  use core::panic;
13
- use std::collections::HashSet;
14
- use std::ffi::{c_int, c_void, CStr, CString};
11
+ use std::ffi::{c_int, c_void, CString};
15
12
  use std::mem::ManuallyDrop;
16
- use std::str::FromStr;
17
13
  use std::sync::{Arc, RwLock};
18
- use std::thread;
19
- use std::time::Duration;
20
14
  use std::{mem, ptr::null_mut};
21
15
 
22
16
  use rb_sys::*;
@@ -25,8 +19,8 @@ use crate::util::*;
25
19
 
26
20
  #[derive(Debug)]
27
21
  pub struct SignalScheduler {
28
- configuration: Option<configuration::Configuration>,
29
- profile: Option<Arc<RwLock<Profile>>>,
22
+ configuration: Configuration,
23
+ profile: Arc<RwLock<Profile>>,
30
24
  }
31
25
 
32
26
  pub struct SignalHandlerArgs {
@@ -34,131 +28,69 @@ pub struct SignalHandlerArgs {
34
28
  context_ruby_thread: VALUE,
35
29
  }
36
30
 
37
- impl SignalScheduler {
38
- fn new() -> Self {
39
- Self {
40
- configuration: None,
41
- profile: None,
42
- }
43
- }
44
-
45
- fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
46
- // Parse arguments
47
- let kwargs: VALUE = Qnil.into();
48
- unsafe {
49
- rb_scan_args(argc, argv, cstr!(":"), &kwargs);
50
- };
51
- let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
52
- unsafe {
53
- rb_get_kwargs(
54
- kwargs,
55
- [
56
- rb_intern(cstr!("interval_ms")),
57
- rb_intern(cstr!("threads")),
58
- rb_intern(cstr!("time_mode")),
59
- rb_intern(cstr!("track_new_threads")),
60
- ]
61
- .as_mut_ptr(),
62
- 0,
63
- 4,
64
- kwargs_values.as_mut_ptr(),
65
- );
66
- };
67
- let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
68
- let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
69
- Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
70
- eprintln!(
71
- "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
72
- interval_ms
73
- );
74
- 49
75
- }))
76
- } else {
77
- Duration::from_millis(49)
78
- };
79
- let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
80
- kwargs_values[1]
81
- } else {
82
- unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
83
- };
84
- let time_mode: configuration::TimeMode = if kwargs_values[2] != Qundef as VALUE {
85
- let specified_mode = unsafe {
86
- let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
87
- let ptr = rb_string_value_ptr(&mut str);
88
- CStr::from_ptr(ptr).to_str().unwrap()
89
- };
90
- configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
91
- // Raise an ArgumentError
92
- unsafe {
93
- rb_raise(
94
- rb_eArgError,
95
- cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
96
- )
97
- }
98
- })
99
- } else {
100
- configuration::TimeMode::CpuTime
101
- };
102
- let track_new_threads: bool = if kwargs_values[3] != Qundef as VALUE {
103
- RTEST(kwargs_values[3])
104
- } else {
105
- false
106
- };
31
+ impl Scheduler for SignalScheduler {
32
+ fn start(&self) -> VALUE {
33
+ self.install_signal_handler();
107
34
 
108
- let mut target_ruby_threads = HashSet::new();
109
- unsafe {
110
- for i in 0..RARRAY_LEN(threads) {
111
- let ruby_thread: VALUE = rb_ary_entry(threads, i);
112
- target_ruby_threads.insert(ruby_thread);
35
+ if let configuration::Threads::Targeted(threads) = &self.configuration.target_ruby_threads {
36
+ for ruby_thread in threads.iter() {
37
+ self.install_timer_to_ruby_thread(*ruby_thread);
113
38
  }
114
39
  }
115
40
 
116
- self.configuration = Some(Configuration {
117
- interval,
118
- target_ruby_threads,
119
- time_mode,
120
- track_new_threads,
121
- });
122
-
123
- Qnil.into()
41
+ Qtrue.into()
124
42
  }
125
43
 
126
- fn start(&mut self, _rbself: VALUE) -> VALUE {
127
- let profile = Arc::new(RwLock::new(Profile::new()));
128
- self.start_profile_buffer_flusher_thread(&profile);
129
- self.install_signal_handler();
44
+ fn stop(&self) -> VALUE {
45
+ // Finalize
46
+ match self.profile.try_write() {
47
+ Ok(mut profile) => {
48
+ profile.flush_temporary_sample_buffer();
49
+ }
50
+ Err(_) => {
51
+ println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
52
+ return Qfalse.into();
53
+ }
54
+ }
130
55
 
131
- TimerInstaller::install_timer_to_ruby_threads(
132
- self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
133
- Arc::clone(&profile),
134
- );
56
+ let profile = self.profile.try_read().unwrap();
57
+ log::debug!("Number of samples: {}", profile.samples.len());
135
58
 
136
- self.profile = Some(profile);
59
+ let serialized = ProfileSerializer::serialize(&profile);
60
+ let serialized = CString::new(serialized).unwrap();
61
+ unsafe { rb_str_new_cstr(serialized.as_ptr()) }
62
+ }
137
63
 
138
- Qtrue.into()
64
+ fn on_new_thread(&self, thread: VALUE) {
65
+ self.install_timer_to_ruby_thread(thread);
139
66
  }
140
67
 
141
- fn stop(&mut self, _rbself: VALUE) -> VALUE {
142
- if let Some(profile) = &self.profile {
143
- // Finalize
144
- match profile.try_write() {
145
- Ok(mut profile) => {
146
- profile.flush_temporary_sample_buffer();
147
- }
148
- Err(_) => {
149
- println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
150
- return Qfalse.into();
151
- }
68
+ fn dmark(&self) {
69
+ match self.profile.read() {
70
+ Ok(profile) => unsafe {
71
+ profile.dmark();
72
+ },
73
+ Err(_) => {
74
+ panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
152
75
  }
76
+ }
77
+ }
153
78
 
154
- let profile = profile.try_read().unwrap();
155
- log::debug!("Number of samples: {}", profile.samples.len());
79
+ fn dfree(&self) {
80
+ // No-op
81
+ }
156
82
 
157
- let serialized = ProfileSerializer::serialize(&profile);
158
- let serialized = CString::new(serialized).unwrap();
159
- unsafe { rb_str_new_cstr(serialized.as_ptr()) }
160
- } else {
161
- panic!("stop() called before start()");
83
+ fn dsize(&self) -> size_t {
84
+ // FIXME: Report something better
85
+ mem::size_of::<Self>() as size_t
86
+ }
87
+ }
88
+
89
+ impl SignalScheduler {
90
+ pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
91
+ Self {
92
+ configuration: configuration.clone(),
93
+ profile,
162
94
  }
163
95
  }
164
96
 
@@ -202,110 +134,62 @@ impl SignalScheduler {
202
134
  }
203
135
  }
204
136
 
205
- fn start_profile_buffer_flusher_thread(&self, profile: &Arc<RwLock<Profile>>) {
206
- let profile = Arc::clone(profile);
207
- thread::spawn(move || loop {
208
- log::trace!("Flushing temporary sample buffer");
209
- match profile.try_write() {
210
- Ok(mut profile) => {
211
- profile.flush_temporary_sample_buffer();
212
- }
213
- Err(_) => {
214
- log::debug!("flusher: Failed to acquire profile lock");
215
- }
216
- }
217
- thread::sleep(Duration::from_millis(500));
137
+ fn install_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
138
+ // NOTE: This Box never gets dropped
139
+ let signal_handler_args = Box::new(SignalHandlerArgs {
140
+ profile: Arc::clone(&self.profile),
141
+ context_ruby_thread: ruby_thread,
218
142
  });
219
- }
220
-
221
- // Ruby Methods
222
-
223
- pub unsafe extern "C" fn rb_initialize(
224
- argc: c_int,
225
- argv: *const VALUE,
226
- rbself: VALUE,
227
- ) -> VALUE {
228
- let mut collector = unsafe { Self::get_struct_from(rbself) };
229
- collector.initialize(argc, argv, rbself)
230
- }
231
-
232
- pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
233
- let mut collector = unsafe { Self::get_struct_from(rbself) };
234
- collector.start(rbself)
235
- }
236
-
237
- pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
238
- let mut collector = unsafe { Self::get_struct_from(rbself) };
239
- collector.stop(rbself)
240
- }
241
-
242
- // Functions for TypedData
243
-
244
- // Extract the SignalScheduler struct from a Ruby object
245
- unsafe fn get_struct_from(obj: VALUE) -> ManuallyDrop<Box<Self>> {
246
- unsafe {
247
- let ptr = rb_check_typeddata(obj, &RBDATA);
248
- ManuallyDrop::new(Box::from_raw(ptr as *mut SignalScheduler))
249
- }
250
- }
251
-
252
- #[allow(non_snake_case)]
253
- pub unsafe extern "C" fn rb_alloc(_rbself: VALUE) -> VALUE {
254
- let collector = Box::new(SignalScheduler::new());
255
- unsafe { Arc::increment_strong_count(&collector) };
256
-
257
- unsafe {
258
- let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
259
- let rb_cSignalScheduler =
260
- rb_define_class_under(rb_mPf2, cstr!("SignalScheduler"), rb_cObject);
261
143
 
262
- // "Wrap" the SignalScheduler struct into a Ruby object
263
- rb_data_typed_object_wrap(
264
- rb_cSignalScheduler,
265
- Box::into_raw(collector) as *mut c_void,
266
- &RBDATA,
267
- )
144
+ // rb_funcall deadlocks when called within a THREAD_EVENT_STARTED hook
145
+ let kernel_thread_id: i32 = i32::try_from(unsafe {
146
+ rb_num2int(rb_funcall(
147
+ ruby_thread,
148
+ rb_intern(cstr!("native_thread_id")), // kernel thread ID
149
+ 0,
150
+ ))
151
+ })
152
+ .unwrap();
153
+
154
+ // Create a signal event
155
+ let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
156
+ // Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
157
+ // "trampoline" the signal as any pthread can receive the signal.
158
+ sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
159
+ sigevent.sigev_notify_thread_id = kernel_thread_id;
160
+ sigevent.sigev_signo = libc::SIGALRM;
161
+ // Pass required args to the signal handler
162
+ sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
163
+
164
+ // Create and configure timer to fire every _interval_ ms of CPU time
165
+ let mut timer: libc::timer_t = unsafe { mem::zeroed() };
166
+ let clockid = match self.configuration.time_mode {
167
+ configuration::TimeMode::CpuTime => unsafe { rb_thread_getcpuclockid(ruby_thread) },
168
+ configuration::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
169
+ };
170
+ let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
171
+ if err != 0 {
172
+ panic!("timer_create failed: {}", err);
268
173
  }
269
- }
270
-
271
- unsafe extern "C" fn dmark(ptr: *mut c_void) {
272
- unsafe {
273
- let collector = ManuallyDrop::new(Box::from_raw(ptr as *mut SignalScheduler));
274
- if let Some(profile) = &collector.profile {
275
- match profile.read() {
276
- Ok(profile) => {
277
- profile.dmark();
278
- }
279
- Err(_) => {
280
- panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
281
- }
282
- }
283
- }
174
+ let itimerspec = Self::duration_to_itimerspec(&self.configuration.interval);
175
+ let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
176
+ if err != 0 {
177
+ panic!("timer_settime failed: {}", err);
284
178
  }
285
- }
286
179
 
287
- unsafe extern "C" fn dfree(ptr: *mut c_void) {
288
- unsafe {
289
- drop(Box::from_raw(ptr as *mut SignalScheduler));
290
- }
180
+ log::debug!("timer registered for thread {}", ruby_thread);
291
181
  }
292
182
 
293
- unsafe extern "C" fn dsize(_: *const c_void) -> size_t {
294
- // FIXME: Report something better
295
- mem::size_of::<SignalScheduler>() as size_t
183
+ fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
184
+ let nanos = duration.as_nanos();
185
+ let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
186
+ let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
187
+
188
+ let mut its: libc::itimerspec = unsafe { mem::zeroed() };
189
+ its.it_interval.tv_sec = seconds_part;
190
+ its.it_interval.tv_nsec = nanos_part;
191
+ its.it_value.tv_sec = seconds_part;
192
+ its.it_value.tv_nsec = nanos_part;
193
+ its
296
194
  }
297
195
  }
298
-
299
- static mut RBDATA: rb_data_type_t = rb_data_type_t {
300
- wrap_struct_name: cstr!("SignalScheduler"),
301
- function: rb_data_type_struct__bindgen_ty_1 {
302
- dmark: Some(SignalScheduler::dmark),
303
- dfree: Some(SignalScheduler::dfree),
304
- dsize: Some(SignalScheduler::dsize),
305
- dcompact: None,
306
- reserved: [null_mut(); 1],
307
- },
308
- parent: null_mut(),
309
- data: null_mut(),
310
- flags: 0,
311
- };