pf2 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -2
- data/Cargo.lock +650 -0
- data/Cargo.toml +3 -0
- data/README.md +110 -13
- data/Rakefile +8 -0
- data/crates/backtrace-sys2/.gitignore +1 -0
- data/crates/backtrace-sys2/Cargo.toml +9 -0
- data/crates/backtrace-sys2/build.rs +48 -0
- data/crates/backtrace-sys2/src/lib.rs +5 -0
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
- data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
- data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
- data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
- data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
- data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
- data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
- data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
- data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
- data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
- data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
- data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
- data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
- data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
- data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
- data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
- data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
- data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
- data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
- data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
- data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
- data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
- data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
- data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
- data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
- data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
- data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
- data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
- data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
- data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
- data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
- data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
- data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
- data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
- data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
- data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
- data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
- data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
- data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
- data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
- data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
- data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
- data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
- data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
- data/ext/pf2/Cargo.toml +25 -0
- data/ext/pf2/build.rs +3 -0
- data/ext/pf2/extconf.rb +6 -1
- data/ext/pf2/src/backtrace.rs +126 -0
- data/ext/pf2/src/lib.rs +15 -0
- data/ext/pf2/src/profile.rs +65 -0
- data/ext/pf2/src/profile_serializer.rs +204 -0
- data/ext/pf2/src/ringbuffer.rs +152 -0
- data/ext/pf2/src/ruby_init.rs +74 -0
- data/ext/pf2/src/sample.rs +66 -0
- data/ext/pf2/src/siginfo_t.c +5 -0
- data/ext/pf2/src/signal_scheduler/configuration.rs +31 -0
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +199 -0
- data/ext/pf2/src/signal_scheduler.rs +311 -0
- data/ext/pf2/src/timer_thread_scheduler.rs +319 -0
- data/ext/pf2/src/util.rs +30 -0
- data/lib/pf2/cli.rb +1 -1
- data/lib/pf2/reporter.rb +48 -16
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +20 -5
- metadata +128 -5
- data/ext/pf2/pf2.c +0 -246
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::ffi::c_void;
|
|
3
|
+
use std::mem;
|
|
4
|
+
use std::mem::ManuallyDrop;
|
|
5
|
+
use std::ptr::null_mut;
|
|
6
|
+
use std::sync::{Mutex, RwLock};
|
|
7
|
+
use std::{collections::HashSet, sync::Arc};
|
|
8
|
+
|
|
9
|
+
use rb_sys::*;
|
|
10
|
+
|
|
11
|
+
use crate::signal_scheduler::SignalHandlerArgs;
|
|
12
|
+
|
|
13
|
+
use super::configuration::Configuration;
|
|
14
|
+
use crate::profile::Profile;
|
|
15
|
+
|
|
16
|
+
// We could avoid deferring the timer creation by combining pthread_getcpuclockid(3) and timer_create(2) here,
|
|
17
|
+
// but we're not doing so since (1) Ruby does not expose the pthread_self() of a Ruby Thread
|
|
18
|
+
// (which is actually stored in th->nt->thread_id), and (2) pthread_getcpuclockid(3) is not portable
|
|
19
|
+
// in the first place (e.g. not available on macOS).
|
|
20
|
+
pub struct TimerInstaller {
|
|
21
|
+
internal: Box<Mutex<Internal>>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
struct Internal {
|
|
25
|
+
configuration: Configuration,
|
|
26
|
+
registered_pthread_ids: HashSet<libc::pthread_t>,
|
|
27
|
+
kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
|
|
28
|
+
profile: Arc<RwLock<Profile>>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl TimerInstaller {
|
|
32
|
+
// Register a callback that gets called when a Ruby Thread is resumed.
|
|
33
|
+
// The callback should create a timer for the thread.
|
|
34
|
+
pub fn install_timer_to_ruby_threads(
|
|
35
|
+
configuration: Configuration,
|
|
36
|
+
profile: Arc<RwLock<Profile>>,
|
|
37
|
+
) {
|
|
38
|
+
let registrar = Self {
|
|
39
|
+
internal: Box::new(Mutex::new(Internal {
|
|
40
|
+
configuration: configuration.clone(),
|
|
41
|
+
registered_pthread_ids: HashSet::new(),
|
|
42
|
+
kernel_thread_id_to_ruby_thread_map: HashMap::new(),
|
|
43
|
+
profile,
|
|
44
|
+
})),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
let ptr = Box::into_raw(registrar.internal);
|
|
48
|
+
unsafe {
|
|
49
|
+
rb_internal_thread_add_event_hook(
|
|
50
|
+
Some(Self::on_thread_resume),
|
|
51
|
+
RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
|
52
|
+
ptr as *mut c_void,
|
|
53
|
+
);
|
|
54
|
+
// Spawn a no-op Thread to fire the event hook
|
|
55
|
+
// (at least 2 Ruby Threads must be active for the RESUMED hook to be fired)
|
|
56
|
+
rb_thread_create(Some(Self::do_nothing), null_mut());
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if configuration.track_new_threads {
|
|
60
|
+
unsafe {
|
|
61
|
+
rb_internal_thread_add_event_hook(
|
|
62
|
+
Some(Self::on_thread_start),
|
|
63
|
+
RUBY_INTERNAL_THREAD_EVENT_STARTED,
|
|
64
|
+
ptr as *mut c_void,
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
unsafe extern "C" fn do_nothing(_: *mut c_void) -> VALUE {
|
|
71
|
+
Qnil.into()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Thread resume callback
|
|
75
|
+
unsafe extern "C" fn on_thread_resume(
|
|
76
|
+
_flag: rb_event_flag_t,
|
|
77
|
+
data: *const rb_internal_thread_event_data,
|
|
78
|
+
custom_data: *mut c_void,
|
|
79
|
+
) {
|
|
80
|
+
// The SignalScheduler (as a Ruby obj) should be passed as custom_data
|
|
81
|
+
let internal =
|
|
82
|
+
unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Internal>)) };
|
|
83
|
+
let mut internal = internal.lock().unwrap();
|
|
84
|
+
|
|
85
|
+
// Check if the current thread is a target Ruby Thread
|
|
86
|
+
let current_ruby_thread: VALUE = unsafe { (*data).thread };
|
|
87
|
+
if !internal
|
|
88
|
+
.configuration
|
|
89
|
+
.target_ruby_threads
|
|
90
|
+
.contains(¤t_ruby_thread)
|
|
91
|
+
{
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if the current thread is already registered
|
|
96
|
+
let current_pthread_id = unsafe { libc::pthread_self() };
|
|
97
|
+
if internal
|
|
98
|
+
.registered_pthread_ids
|
|
99
|
+
.contains(¤t_pthread_id)
|
|
100
|
+
{
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Record the pthread ID of the current thread
|
|
105
|
+
internal.registered_pthread_ids.insert(current_pthread_id);
|
|
106
|
+
// Keep a mapping from kernel thread ID to Ruby Thread
|
|
107
|
+
internal
|
|
108
|
+
.kernel_thread_id_to_ruby_thread_map
|
|
109
|
+
.insert(unsafe { libc::gettid() }, current_ruby_thread);
|
|
110
|
+
|
|
111
|
+
Self::register_timer_to_current_thread(
|
|
112
|
+
&internal.configuration,
|
|
113
|
+
&internal.profile,
|
|
114
|
+
&internal.kernel_thread_id_to_ruby_thread_map,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// TODO: Remove the hook when all threads have been registered
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Thread resume callback
|
|
121
|
+
unsafe extern "C" fn on_thread_start(
|
|
122
|
+
_flag: rb_event_flag_t,
|
|
123
|
+
data: *const rb_internal_thread_event_data,
|
|
124
|
+
custom_data: *mut c_void,
|
|
125
|
+
) {
|
|
126
|
+
// The SignalScheduler (as a Ruby obj) should be passed as custom_data
|
|
127
|
+
let internal =
|
|
128
|
+
unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Internal>)) };
|
|
129
|
+
let mut internal = internal.lock().unwrap();
|
|
130
|
+
|
|
131
|
+
let current_ruby_thread: VALUE = unsafe { (*data).thread };
|
|
132
|
+
internal
|
|
133
|
+
.configuration
|
|
134
|
+
.target_ruby_threads
|
|
135
|
+
.insert(current_ruby_thread);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Creates a new POSIX timer which invocates sampling for the thread that called this function.
|
|
139
|
+
fn register_timer_to_current_thread(
|
|
140
|
+
configuration: &Configuration,
|
|
141
|
+
profile: &Arc<RwLock<Profile>>,
|
|
142
|
+
kernel_thread_id_to_ruby_thread_map: &HashMap<libc::pid_t, VALUE>,
|
|
143
|
+
) {
|
|
144
|
+
let current_pthread_id = unsafe { libc::pthread_self() };
|
|
145
|
+
let context_ruby_thread: VALUE = unsafe {
|
|
146
|
+
*(kernel_thread_id_to_ruby_thread_map
|
|
147
|
+
.get(&(libc::gettid()))
|
|
148
|
+
.unwrap())
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// NOTE: This Box is never dropped
|
|
152
|
+
let signal_handler_args = Box::new(SignalHandlerArgs {
|
|
153
|
+
profile: Arc::clone(profile),
|
|
154
|
+
context_ruby_thread,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Create a signal event
|
|
158
|
+
let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
|
|
159
|
+
// Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
|
|
160
|
+
// "tranpoline" the signal as any pthread can receive the signal.
|
|
161
|
+
sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
|
|
162
|
+
sigevent.sigev_notify_thread_id =
|
|
163
|
+
unsafe { libc::syscall(libc::SYS_gettid).try_into().unwrap() }; // The kernel thread ID
|
|
164
|
+
sigevent.sigev_signo = libc::SIGALRM;
|
|
165
|
+
// Pass required args to the signal handler
|
|
166
|
+
sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
|
|
167
|
+
|
|
168
|
+
// Create and configure timer to fire every _interval_ ms of CPU time
|
|
169
|
+
let mut timer: libc::timer_t = unsafe { mem::zeroed() };
|
|
170
|
+
let clockid = match configuration.time_mode {
|
|
171
|
+
crate::signal_scheduler::TimeMode::CpuTime => libc::CLOCK_THREAD_CPUTIME_ID,
|
|
172
|
+
crate::signal_scheduler::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
|
|
173
|
+
};
|
|
174
|
+
let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
|
|
175
|
+
if err != 0 {
|
|
176
|
+
panic!("timer_create failed: {}", err);
|
|
177
|
+
}
|
|
178
|
+
let itimerspec = Self::duration_to_itimerspec(&configuration.interval);
|
|
179
|
+
let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
|
|
180
|
+
if err != 0 {
|
|
181
|
+
panic!("timer_settime failed: {}", err);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
log::debug!("timer registered for thread {}", current_pthread_id);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
|
|
188
|
+
let nanos = duration.as_nanos();
|
|
189
|
+
let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
|
|
190
|
+
let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
|
|
191
|
+
|
|
192
|
+
let mut its: libc::itimerspec = unsafe { mem::zeroed() };
|
|
193
|
+
its.it_interval.tv_sec = seconds_part;
|
|
194
|
+
its.it_interval.tv_nsec = nanos_part;
|
|
195
|
+
its.it_value.tv_sec = seconds_part;
|
|
196
|
+
its.it_value.tv_nsec = nanos_part;
|
|
197
|
+
its
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#![deny(unsafe_op_in_unsafe_fn)]
|
|
2
|
+
|
|
3
|
+
mod configuration;
|
|
4
|
+
mod timer_installer;
|
|
5
|
+
|
|
6
|
+
use self::configuration::{Configuration, TimeMode};
|
|
7
|
+
use self::timer_installer::TimerInstaller;
|
|
8
|
+
use crate::profile::Profile;
|
|
9
|
+
use crate::profile_serializer::ProfileSerializer;
|
|
10
|
+
use crate::sample::Sample;
|
|
11
|
+
|
|
12
|
+
use core::panic;
|
|
13
|
+
use std::collections::HashSet;
|
|
14
|
+
use std::ffi::{c_int, c_void, CStr, CString};
|
|
15
|
+
use std::mem::ManuallyDrop;
|
|
16
|
+
use std::str::FromStr;
|
|
17
|
+
use std::sync::{Arc, RwLock};
|
|
18
|
+
use std::thread;
|
|
19
|
+
use std::time::Duration;
|
|
20
|
+
use std::{mem, ptr::null_mut};
|
|
21
|
+
|
|
22
|
+
use rb_sys::*;
|
|
23
|
+
|
|
24
|
+
use crate::util::*;
|
|
25
|
+
|
|
26
|
+
#[derive(Debug)]
|
|
27
|
+
pub struct SignalScheduler {
|
|
28
|
+
configuration: Option<configuration::Configuration>,
|
|
29
|
+
profile: Option<Arc<RwLock<Profile>>>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub struct SignalHandlerArgs {
|
|
33
|
+
profile: Arc<RwLock<Profile>>,
|
|
34
|
+
context_ruby_thread: VALUE,
|
|
35
|
+
}
|
|
36
|
+
|
|
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
|
+
};
|
|
107
|
+
|
|
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);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
self.configuration = Some(Configuration {
|
|
117
|
+
interval,
|
|
118
|
+
target_ruby_threads,
|
|
119
|
+
time_mode,
|
|
120
|
+
track_new_threads,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
Qnil.into()
|
|
124
|
+
}
|
|
125
|
+
|
|
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();
|
|
130
|
+
|
|
131
|
+
TimerInstaller::install_timer_to_ruby_threads(
|
|
132
|
+
self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
|
|
133
|
+
Arc::clone(&profile),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
self.profile = Some(profile);
|
|
137
|
+
|
|
138
|
+
Qtrue.into()
|
|
139
|
+
}
|
|
140
|
+
|
|
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
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let profile = profile.try_read().unwrap();
|
|
155
|
+
log::debug!("Number of samples: {}", profile.samples.len());
|
|
156
|
+
|
|
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()");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Install signal handler for profiling events to the current process.
|
|
166
|
+
fn install_signal_handler(&self) {
|
|
167
|
+
let mut sa: libc::sigaction = unsafe { mem::zeroed() };
|
|
168
|
+
sa.sa_sigaction = Self::signal_handler as usize;
|
|
169
|
+
sa.sa_flags = libc::SA_SIGINFO;
|
|
170
|
+
let err = unsafe { libc::sigaction(libc::SIGALRM, &sa, null_mut()) };
|
|
171
|
+
if err != 0 {
|
|
172
|
+
panic!("sigaction failed: {}", err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Respond to the signal and collect a sample.
|
|
177
|
+
// This function is called when a timer fires.
|
|
178
|
+
//
|
|
179
|
+
// Expected to be async-signal-safe, but the current implementation is not.
|
|
180
|
+
extern "C" fn signal_handler(
|
|
181
|
+
_sig: c_int,
|
|
182
|
+
info: *mut libc::siginfo_t,
|
|
183
|
+
_ucontext: *mut libc::ucontext_t,
|
|
184
|
+
) {
|
|
185
|
+
let args = unsafe {
|
|
186
|
+
let ptr = extract_si_value_sival_ptr(info) as *mut SignalHandlerArgs;
|
|
187
|
+
ManuallyDrop::new(Box::from_raw(ptr))
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
let mut profile = match args.profile.try_write() {
|
|
191
|
+
Ok(profile) => profile,
|
|
192
|
+
Err(_) => {
|
|
193
|
+
// FIXME: Do we want to properly collect GC samples? I don't know yet.
|
|
194
|
+
log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
|
|
200
|
+
if profile.temporary_sample_buffer.push(sample).is_err() {
|
|
201
|
+
log::debug!("Temporary sample buffer full. Dropping sample.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
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));
|
|
218
|
+
});
|
|
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
|
+
|
|
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
|
+
)
|
|
268
|
+
}
|
|
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
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
unsafe extern "C" fn dfree(ptr: *mut c_void) {
|
|
288
|
+
unsafe {
|
|
289
|
+
drop(Box::from_raw(ptr as *mut SignalScheduler));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
unsafe extern "C" fn dsize(_: *const c_void) -> size_t {
|
|
294
|
+
// FIXME: Report something better
|
|
295
|
+
mem::size_of::<SignalScheduler>() as size_t
|
|
296
|
+
}
|
|
297
|
+
}
|
|
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
|
+
};
|