pf2 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- };