pf2 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Cargo.lock +2 -2
- data/README.md +15 -3
- data/ext/pf2/src/lib.rs +2 -0
- data/ext/pf2/src/profile_serializer.rs +77 -33
- data/ext/pf2/src/ruby_init.rs +9 -40
- data/ext/pf2/src/ruby_internal_apis.rs +24 -1
- data/ext/pf2/src/scheduler.rs +10 -0
- data/ext/pf2/src/session/configuration.rs +106 -0
- data/ext/pf2/src/session/new_thread_watcher.rs +80 -0
- data/ext/pf2/src/session/ruby_object.rs +90 -0
- data/ext/pf2/src/session.rs +227 -0
- data/ext/pf2/src/signal_scheduler.rs +105 -221
- data/ext/pf2/src/timer_thread_scheduler.rs +92 -240
- data/lib/pf2/cli.rb +69 -7
- data/lib/pf2/reporter.rb +105 -4
- data/lib/pf2/serve.rb +60 -0
- data/lib/pf2/session.rb +7 -0
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +7 -14
- metadata +23 -4
- data/ext/pf2/src/signal_scheduler/configuration.rs +0 -31
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +0 -145
@@ -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::
|
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:
|
29
|
-
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
|
39
|
-
|
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_all_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_all_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
|
109
|
-
|
110
|
-
|
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
|
-
|
117
|
-
interval,
|
118
|
-
target_ruby_threads,
|
119
|
-
time_mode,
|
120
|
-
track_all_threads,
|
121
|
-
});
|
122
|
-
|
123
|
-
Qnil.into()
|
41
|
+
Qtrue.into()
|
124
42
|
}
|
125
43
|
|
126
|
-
fn
|
127
|
-
|
128
|
-
self.
|
129
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
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
|
-
|
64
|
+
fn on_new_thread(&self, thread: VALUE) {
|
65
|
+
self.install_timer_to_ruby_thread(thread);
|
139
66
|
}
|
140
67
|
|
141
|
-
fn
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
155
|
-
|
79
|
+
fn dfree(&self) {
|
80
|
+
// No-op
|
81
|
+
}
|
156
82
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
272
|
-
|
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
|
-
|
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
|
-
|
294
|
-
|
295
|
-
|
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
|
-
};
|