pf2 0.2.0 → 0.4.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 +31 -2
- data/Cargo.lock +186 -17
- data/Cargo.toml +1 -1
- data/README.md +18 -6
- 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 +1 -0
- data/ext/pf2/src/backtrace.rs +127 -0
- data/ext/pf2/src/lib.rs +3 -0
- data/ext/pf2/src/profile.rs +16 -1
- data/ext/pf2/src/profile_serializer.rs +95 -21
- data/ext/pf2/src/ringbuffer.rs +7 -0
- data/ext/pf2/src/ruby_init.rs +18 -6
- data/ext/pf2/src/ruby_internal_apis.rs +47 -0
- data/ext/pf2/src/sample.rs +22 -1
- data/ext/pf2/src/signal_scheduler/configuration.rs +7 -0
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +79 -126
- data/ext/pf2/src/signal_scheduler.rs +95 -26
- data/ext/pf2/src/timer_thread_scheduler.rs +88 -12
- data/ext/pf2/src/util.rs +2 -2
- data/lib/pf2/reporter.rb +12 -5
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +3 -6
- metadata +97 -6
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
use std::collections::
|
|
1
|
+
use std::collections::HashSet;
|
|
2
2
|
use std::ffi::c_void;
|
|
3
3
|
use std::mem;
|
|
4
4
|
use std::mem::ManuallyDrop;
|
|
5
5
|
use std::ptr::null_mut;
|
|
6
|
+
use std::sync::Arc;
|
|
6
7
|
use std::sync::{Mutex, RwLock};
|
|
7
|
-
use std::{collections::HashSet, sync::Arc};
|
|
8
8
|
|
|
9
9
|
use rb_sys::*;
|
|
10
10
|
|
|
11
|
-
use crate::signal_scheduler::SignalHandlerArgs;
|
|
12
|
-
|
|
13
11
|
use super::configuration::Configuration;
|
|
14
12
|
use crate::profile::Profile;
|
|
13
|
+
use crate::ruby_internal_apis::rb_thread_getcpuclockid;
|
|
14
|
+
use crate::signal_scheduler::{cstr, SignalHandlerArgs};
|
|
15
15
|
|
|
16
|
-
|
|
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).
|
|
16
|
+
#[derive(Debug)]
|
|
20
17
|
pub struct TimerInstaller {
|
|
21
|
-
|
|
18
|
+
inner: Box<Mutex<Inner>>,
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
#[derive(Debug)]
|
|
22
|
+
struct Inner {
|
|
25
23
|
configuration: Configuration,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
|
|
29
|
-
profile: Arc<RwLock<Profile>>,
|
|
24
|
+
pub profile: Arc<RwLock<Profile>>,
|
|
25
|
+
known_threads: HashSet<VALUE>,
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
impl TimerInstaller {
|
|
@@ -34,159 +30,116 @@ impl TimerInstaller {
|
|
|
34
30
|
// The callback should create a timer for the thread.
|
|
35
31
|
pub fn install_timer_to_ruby_threads(
|
|
36
32
|
configuration: Configuration,
|
|
37
|
-
ruby_threads: &HashSet<VALUE>,
|
|
38
33
|
profile: Arc<RwLock<Profile>>,
|
|
39
|
-
track_new_threads: bool,
|
|
40
34
|
) {
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
configuration,
|
|
44
|
-
target_ruby_threads: ruby_threads.clone(),
|
|
45
|
-
registered_pthread_ids: HashSet::new(),
|
|
46
|
-
kernel_thread_id_to_ruby_thread_map: HashMap::new(),
|
|
35
|
+
let installer = Self {
|
|
36
|
+
inner: Box::new(Mutex::new(Inner {
|
|
37
|
+
configuration: configuration.clone(),
|
|
47
38
|
profile,
|
|
39
|
+
known_threads: HashSet::new(),
|
|
48
40
|
})),
|
|
49
41
|
};
|
|
50
42
|
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Spawn a no-op Thread to fire the event hook
|
|
59
|
-
// (at least 2 Ruby Threads must be active for the RESUMED hook to be fired)
|
|
60
|
-
rb_thread_create(Some(Self::do_nothing), null_mut());
|
|
61
|
-
};
|
|
43
|
+
if let Ok(mut inner) = installer.inner.try_lock() {
|
|
44
|
+
for ruby_thread in configuration.target_ruby_threads.iter() {
|
|
45
|
+
let ruby_thread: VALUE = *ruby_thread;
|
|
46
|
+
inner.known_threads.insert(ruby_thread);
|
|
47
|
+
inner.register_timer_to_ruby_thread(ruby_thread);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
62
50
|
|
|
63
|
-
if
|
|
51
|
+
if configuration.track_all_threads {
|
|
52
|
+
let ptr = Box::into_raw(installer.inner);
|
|
64
53
|
unsafe {
|
|
54
|
+
// TODO: Clean up this hook when the profiling session ends
|
|
65
55
|
rb_internal_thread_add_event_hook(
|
|
66
|
-
Some(Self::
|
|
67
|
-
|
|
56
|
+
Some(Self::on_thread_resume),
|
|
57
|
+
RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
|
68
58
|
ptr as *mut c_void,
|
|
69
59
|
);
|
|
70
60
|
};
|
|
71
61
|
}
|
|
72
62
|
}
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
Qnil.into()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Thread resume callback
|
|
64
|
+
// Thread start callback
|
|
79
65
|
unsafe extern "C" fn on_thread_resume(
|
|
80
66
|
_flag: rb_event_flag_t,
|
|
81
67
|
data: *const rb_internal_thread_event_data,
|
|
82
68
|
custom_data: *mut c_void,
|
|
83
69
|
) {
|
|
84
|
-
|
|
85
|
-
let internal =
|
|
86
|
-
unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Internal>)) };
|
|
87
|
-
let mut internal = internal.lock().unwrap();
|
|
88
|
-
|
|
89
|
-
// Check if the current thread is a target Ruby Thread
|
|
90
|
-
let current_ruby_thread: VALUE = unsafe { (*data).thread };
|
|
91
|
-
if !internal.target_ruby_threads.contains(¤t_ruby_thread) {
|
|
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
|
-
}
|
|
70
|
+
let ruby_thread: VALUE = unsafe { (*data).thread };
|
|
119
71
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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();
|
|
72
|
+
// A pointer to Box<Inner> is passed as custom_data
|
|
73
|
+
let inner = unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Inner>)) };
|
|
74
|
+
let mut inner = inner.lock().unwrap();
|
|
130
75
|
|
|
131
|
-
|
|
132
|
-
|
|
76
|
+
if !inner.known_threads.contains(&ruby_thread) {
|
|
77
|
+
inner.known_threads.insert(ruby_thread);
|
|
78
|
+
// Install a timer for the thread
|
|
79
|
+
inner.register_timer_to_ruby_thread(ruby_thread);
|
|
80
|
+
}
|
|
133
81
|
}
|
|
82
|
+
}
|
|
134
83
|
|
|
135
|
-
|
|
136
|
-
fn
|
|
137
|
-
configuration: &Configuration,
|
|
138
|
-
profile: &Arc<RwLock<Profile>>,
|
|
139
|
-
kernel_thread_id_to_ruby_thread_map: &HashMap<libc::pid_t, VALUE>,
|
|
140
|
-
) {
|
|
141
|
-
let current_pthread_id = unsafe { libc::pthread_self() };
|
|
142
|
-
let context_ruby_thread: VALUE = unsafe {
|
|
143
|
-
*(kernel_thread_id_to_ruby_thread_map
|
|
144
|
-
.get(&(libc::gettid()))
|
|
145
|
-
.unwrap())
|
|
146
|
-
};
|
|
147
|
-
|
|
84
|
+
impl Inner {
|
|
85
|
+
fn register_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
|
|
148
86
|
// NOTE: This Box is never dropped
|
|
149
87
|
let signal_handler_args = Box::new(SignalHandlerArgs {
|
|
150
|
-
profile: Arc::clone(profile),
|
|
151
|
-
context_ruby_thread,
|
|
88
|
+
profile: Arc::clone(&self.profile),
|
|
89
|
+
context_ruby_thread: ruby_thread,
|
|
152
90
|
});
|
|
153
91
|
|
|
92
|
+
// rb_funcall deadlocks when called within a THREAD_EVENT_STARTED hook
|
|
93
|
+
let kernel_thread_id: i32 = i32::try_from(unsafe {
|
|
94
|
+
rb_num2int(rb_funcall(
|
|
95
|
+
ruby_thread,
|
|
96
|
+
rb_intern(cstr!("native_thread_id")), // kernel thread ID
|
|
97
|
+
0,
|
|
98
|
+
))
|
|
99
|
+
})
|
|
100
|
+
.unwrap();
|
|
101
|
+
|
|
154
102
|
// Create a signal event
|
|
155
103
|
let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
|
|
156
104
|
// Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
|
|
157
|
-
// "
|
|
105
|
+
// "trampoline" the signal as any pthread can receive the signal.
|
|
158
106
|
sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
|
|
159
|
-
sigevent.sigev_notify_thread_id =
|
|
160
|
-
unsafe { libc::syscall(libc::SYS_gettid).try_into().unwrap() }; // The kernel thread ID
|
|
107
|
+
sigevent.sigev_notify_thread_id = kernel_thread_id;
|
|
161
108
|
sigevent.sigev_signo = libc::SIGALRM;
|
|
162
109
|
// Pass required args to the signal handler
|
|
163
110
|
sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
|
|
164
111
|
|
|
165
|
-
// Create and configure timer to fire every
|
|
112
|
+
// Create and configure timer to fire every _interval_ ms of CPU time
|
|
166
113
|
let mut timer: libc::timer_t = unsafe { mem::zeroed() };
|
|
167
|
-
match configuration.time_mode {
|
|
168
|
-
crate::signal_scheduler::TimeMode::CpuTime => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if err != 0 {
|
|
173
|
-
panic!("timer_create failed: {}", err);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
crate::signal_scheduler::TimeMode::WallTime => {
|
|
177
|
-
todo!("WallTime is not supported yet");
|
|
178
|
-
}
|
|
114
|
+
let clockid = match self.configuration.time_mode {
|
|
115
|
+
crate::signal_scheduler::TimeMode::CpuTime => unsafe {
|
|
116
|
+
rb_thread_getcpuclockid(ruby_thread)
|
|
117
|
+
},
|
|
118
|
+
crate::signal_scheduler::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
|
|
179
119
|
};
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let err = unsafe { libc::timer_settime(timer, 0, &
|
|
120
|
+
let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
|
|
121
|
+
if err != 0 {
|
|
122
|
+
panic!("timer_create failed: {}", err);
|
|
123
|
+
}
|
|
124
|
+
let itimerspec = Self::duration_to_itimerspec(&self.configuration.interval);
|
|
125
|
+
let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
|
|
186
126
|
if err != 0 {
|
|
187
127
|
panic!("timer_settime failed: {}", err);
|
|
188
128
|
}
|
|
189
129
|
|
|
190
|
-
log::debug!("timer registered for thread {}",
|
|
130
|
+
log::debug!("timer registered for thread {}", ruby_thread);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
|
|
134
|
+
let nanos = duration.as_nanos();
|
|
135
|
+
let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
|
|
136
|
+
let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
|
|
137
|
+
|
|
138
|
+
let mut its: libc::itimerspec = unsafe { mem::zeroed() };
|
|
139
|
+
its.it_interval.tv_sec = seconds_part;
|
|
140
|
+
its.it_interval.tv_nsec = nanos_part;
|
|
141
|
+
its.it_value.tv_sec = seconds_part;
|
|
142
|
+
its.it_value.tv_nsec = nanos_part;
|
|
143
|
+
its
|
|
191
144
|
}
|
|
192
145
|
}
|
|
@@ -11,8 +11,9 @@ use crate::sample::Sample;
|
|
|
11
11
|
|
|
12
12
|
use core::panic;
|
|
13
13
|
use std::collections::HashSet;
|
|
14
|
-
use std::ffi::{c_int, c_void, CString};
|
|
14
|
+
use std::ffi::{c_int, c_void, CStr, CString};
|
|
15
15
|
use std::mem::ManuallyDrop;
|
|
16
|
+
use std::str::FromStr;
|
|
16
17
|
use std::sync::{Arc, RwLock};
|
|
17
18
|
use std::thread;
|
|
18
19
|
use std::time::Duration;
|
|
@@ -24,7 +25,7 @@ use crate::util::*;
|
|
|
24
25
|
|
|
25
26
|
#[derive(Debug)]
|
|
26
27
|
pub struct SignalScheduler {
|
|
27
|
-
configuration: configuration::Configuration
|
|
28
|
+
configuration: Option<configuration::Configuration>,
|
|
28
29
|
profile: Option<Arc<RwLock<Profile>>>,
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -36,37 +37,100 @@ pub struct SignalHandlerArgs {
|
|
|
36
37
|
impl SignalScheduler {
|
|
37
38
|
fn new() -> Self {
|
|
38
39
|
Self {
|
|
39
|
-
configuration:
|
|
40
|
-
time_mode: TimeMode::CpuTime,
|
|
41
|
-
},
|
|
40
|
+
configuration: None,
|
|
42
41
|
profile: None,
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
fn
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
+
};
|
|
57
107
|
|
|
58
108
|
let mut target_ruby_threads = HashSet::new();
|
|
59
109
|
unsafe {
|
|
60
|
-
for i in 0..RARRAY_LEN(
|
|
61
|
-
let ruby_thread: VALUE = rb_ary_entry(
|
|
110
|
+
for i in 0..RARRAY_LEN(threads) {
|
|
111
|
+
let ruby_thread: VALUE = rb_ary_entry(threads, i);
|
|
62
112
|
target_ruby_threads.insert(ruby_thread);
|
|
63
113
|
}
|
|
64
114
|
}
|
|
115
|
+
|
|
116
|
+
self.configuration = Some(Configuration {
|
|
117
|
+
interval,
|
|
118
|
+
target_ruby_threads,
|
|
119
|
+
time_mode,
|
|
120
|
+
track_all_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
|
+
|
|
65
131
|
TimerInstaller::install_timer_to_ruby_threads(
|
|
66
|
-
self.configuration.clone(),
|
|
67
|
-
&target_ruby_threads,
|
|
132
|
+
self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
|
|
68
133
|
Arc::clone(&profile),
|
|
69
|
-
track_new_threads,
|
|
70
134
|
);
|
|
71
135
|
|
|
72
136
|
self.profile = Some(profile);
|
|
@@ -132,7 +196,7 @@ impl SignalScheduler {
|
|
|
132
196
|
}
|
|
133
197
|
};
|
|
134
198
|
|
|
135
|
-
let sample = Sample::capture(args.context_ruby_thread); // NOT async-signal-safe
|
|
199
|
+
let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
|
|
136
200
|
if profile.temporary_sample_buffer.push(sample).is_err() {
|
|
137
201
|
log::debug!("Temporary sample buffer full. Dropping sample.");
|
|
138
202
|
}
|
|
@@ -156,13 +220,18 @@ impl SignalScheduler {
|
|
|
156
220
|
|
|
157
221
|
// Ruby Methods
|
|
158
222
|
|
|
159
|
-
pub unsafe extern "C" fn
|
|
223
|
+
pub unsafe extern "C" fn rb_initialize(
|
|
224
|
+
argc: c_int,
|
|
225
|
+
argv: *const VALUE,
|
|
160
226
|
rbself: VALUE,
|
|
161
|
-
ruby_threads: VALUE,
|
|
162
|
-
track_new_threads: VALUE,
|
|
163
227
|
) -> VALUE {
|
|
164
228
|
let mut collector = unsafe { Self::get_struct_from(rbself) };
|
|
165
|
-
collector.
|
|
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)
|
|
166
235
|
}
|
|
167
236
|
|
|
168
237
|
pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#![deny(unsafe_op_in_unsafe_fn)]
|
|
2
2
|
|
|
3
|
-
use std::ffi::{c_void, CString};
|
|
3
|
+
use std::ffi::{c_int, c_void, CStr, CString};
|
|
4
4
|
use std::mem::ManuallyDrop;
|
|
5
5
|
use std::ptr::null_mut;
|
|
6
6
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
@@ -18,6 +18,7 @@ use crate::util::*;
|
|
|
18
18
|
#[derive(Clone, Debug)]
|
|
19
19
|
pub struct TimerThreadScheduler {
|
|
20
20
|
ruby_threads: Arc<RwLock<Vec<VALUE>>>,
|
|
21
|
+
interval: Option<Arc<Duration>>,
|
|
21
22
|
profile: Option<Arc<RwLock<Profile>>>,
|
|
22
23
|
stop_requested: Arc<AtomicBool>,
|
|
23
24
|
}
|
|
@@ -32,26 +33,89 @@ impl TimerThreadScheduler {
|
|
|
32
33
|
fn new() -> Self {
|
|
33
34
|
TimerThreadScheduler {
|
|
34
35
|
ruby_threads: Arc::new(RwLock::new(vec![])),
|
|
36
|
+
interval: None,
|
|
35
37
|
profile: None,
|
|
36
38
|
stop_requested: Arc::new(AtomicBool::new(false)),
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
fn
|
|
41
|
-
//
|
|
42
|
-
let
|
|
42
|
+
fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
|
|
43
|
+
// Parse arguments
|
|
44
|
+
let kwargs: VALUE = Qnil.into();
|
|
43
45
|
unsafe {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
|
|
47
|
+
};
|
|
48
|
+
let mut kwargs_values: [VALUE; 3] = [Qnil.into(); 3];
|
|
49
|
+
unsafe {
|
|
50
|
+
rb_get_kwargs(
|
|
51
|
+
kwargs,
|
|
52
|
+
[
|
|
53
|
+
rb_intern(cstr!("interval_ms")),
|
|
54
|
+
rb_intern(cstr!("threads")),
|
|
55
|
+
rb_intern(cstr!("time_mode")),
|
|
56
|
+
]
|
|
57
|
+
.as_mut_ptr(),
|
|
58
|
+
0,
|
|
59
|
+
3,
|
|
60
|
+
kwargs_values.as_mut_ptr(),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
|
|
64
|
+
let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
|
|
65
|
+
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
|
|
66
|
+
eprintln!(
|
|
67
|
+
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
|
|
68
|
+
interval_ms
|
|
69
|
+
);
|
|
70
|
+
49
|
|
71
|
+
}))
|
|
72
|
+
} else {
|
|
73
|
+
Duration::from_millis(49)
|
|
74
|
+
};
|
|
75
|
+
let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
|
|
76
|
+
kwargs_values[1]
|
|
77
|
+
} else {
|
|
78
|
+
unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
|
|
79
|
+
};
|
|
80
|
+
if kwargs_values[2] != Qundef as VALUE {
|
|
81
|
+
let specified_mode = unsafe {
|
|
82
|
+
let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
|
|
83
|
+
let ptr = rb_string_value_ptr(&mut str);
|
|
84
|
+
CStr::from_ptr(ptr).to_str().unwrap()
|
|
85
|
+
};
|
|
86
|
+
if specified_mode != "wall" {
|
|
87
|
+
// Raise an ArgumentError
|
|
88
|
+
unsafe {
|
|
89
|
+
rb_raise(
|
|
90
|
+
rb_eArgError,
|
|
91
|
+
cstr!("TimerThreadScheduler only supports :wall mode."),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
46
94
|
}
|
|
47
95
|
}
|
|
48
96
|
|
|
97
|
+
let mut target_ruby_threads = Vec::new();
|
|
98
|
+
unsafe {
|
|
99
|
+
for i in 0..RARRAY_LEN(threads) {
|
|
100
|
+
let ruby_thread: VALUE = rb_ary_entry(threads, i);
|
|
101
|
+
target_ruby_threads.push(ruby_thread);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
self.interval = Some(Arc::new(interval));
|
|
106
|
+
self.ruby_threads = Arc::new(RwLock::new(target_ruby_threads.into_iter().collect()));
|
|
107
|
+
|
|
108
|
+
Qnil.into()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn start(&mut self, _rbself: VALUE) -> VALUE {
|
|
49
112
|
// Create Profile
|
|
50
113
|
let profile = Arc::new(RwLock::new(Profile::new()));
|
|
51
114
|
self.start_profile_buffer_flusher_thread(&profile);
|
|
52
115
|
|
|
53
116
|
// Start monitoring thread
|
|
54
117
|
let stop_requested = Arc::clone(&self.stop_requested);
|
|
118
|
+
let interval = Arc::clone(self.interval.as_ref().unwrap());
|
|
55
119
|
let postponed_job_args: Box<PostponedJobArgs> = Box::new(PostponedJobArgs {
|
|
56
120
|
ruby_threads: Arc::clone(&self.ruby_threads),
|
|
57
121
|
profile: Arc::clone(&profile),
|
|
@@ -63,7 +127,9 @@ impl TimerThreadScheduler {
|
|
|
63
127
|
Box::into_raw(postponed_job_args) as *mut c_void, // FIXME: leak
|
|
64
128
|
)
|
|
65
129
|
};
|
|
66
|
-
thread::spawn(move ||
|
|
130
|
+
thread::spawn(move || {
|
|
131
|
+
Self::thread_main_loop(stop_requested, interval, postponed_job_handle)
|
|
132
|
+
});
|
|
67
133
|
|
|
68
134
|
self.profile = Some(profile);
|
|
69
135
|
|
|
@@ -72,6 +138,7 @@ impl TimerThreadScheduler {
|
|
|
72
138
|
|
|
73
139
|
fn thread_main_loop(
|
|
74
140
|
stop_requested: Arc<AtomicBool>,
|
|
141
|
+
interval: Arc<Duration>,
|
|
75
142
|
postponed_job_handle: rb_postponed_job_handle_t,
|
|
76
143
|
) {
|
|
77
144
|
loop {
|
|
@@ -81,8 +148,8 @@ impl TimerThreadScheduler {
|
|
|
81
148
|
unsafe {
|
|
82
149
|
rb_postponed_job_trigger(postponed_job_handle);
|
|
83
150
|
}
|
|
84
|
-
|
|
85
|
-
thread::sleep(
|
|
151
|
+
|
|
152
|
+
thread::sleep(*interval);
|
|
86
153
|
}
|
|
87
154
|
}
|
|
88
155
|
|
|
@@ -136,7 +203,7 @@ impl TimerThreadScheduler {
|
|
|
136
203
|
continue;
|
|
137
204
|
}
|
|
138
205
|
|
|
139
|
-
let sample = Sample::capture(*ruby_thread);
|
|
206
|
+
let sample = Sample::capture(*ruby_thread, &profile.backtrace_state);
|
|
140
207
|
if profile.temporary_sample_buffer.push(sample).is_err() {
|
|
141
208
|
log::debug!("Temporary sample buffer full. Dropping sample.");
|
|
142
209
|
}
|
|
@@ -164,10 +231,19 @@ impl TimerThreadScheduler {
|
|
|
164
231
|
|
|
165
232
|
// Ruby Methods
|
|
166
233
|
|
|
234
|
+
pub unsafe extern "C" fn rb_initialize(
|
|
235
|
+
argc: c_int,
|
|
236
|
+
argv: *const VALUE,
|
|
237
|
+
rbself: VALUE,
|
|
238
|
+
) -> VALUE {
|
|
239
|
+
let mut collector = Self::get_struct_from(rbself);
|
|
240
|
+
collector.initialize(argc, argv, rbself)
|
|
241
|
+
}
|
|
242
|
+
|
|
167
243
|
// SampleCollector.start
|
|
168
|
-
pub unsafe extern "C" fn rb_start(rbself: VALUE
|
|
244
|
+
pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
|
|
169
245
|
let mut collector = Self::get_struct_from(rbself);
|
|
170
|
-
collector.start(rbself
|
|
246
|
+
collector.start(rbself)
|
|
171
247
|
}
|
|
172
248
|
|
|
173
249
|
// SampleCollector.stop
|
data/ext/pf2/src/util.rs
CHANGED
|
@@ -13,10 +13,10 @@ pub(crate) use cstr;
|
|
|
13
13
|
pub type RubyCFunc = unsafe extern "C" fn() -> VALUE;
|
|
14
14
|
|
|
15
15
|
// TODO: rewrite as macro
|
|
16
|
-
pub fn
|
|
16
|
+
pub fn to_ruby_cfunc_with_no_args<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
|
|
17
17
|
unsafe { transmute::<unsafe extern "C" fn(T) -> VALUE, RubyCFunc>(f) }
|
|
18
18
|
}
|
|
19
|
-
pub fn
|
|
19
|
+
pub fn to_ruby_cfunc_with_args<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
|
|
20
20
|
unsafe { transmute::<unsafe extern "C" fn(T, U, V) -> VALUE, RubyCFunc>(f) }
|
|
21
21
|
}
|
|
22
22
|
|
data/lib/pf2/reporter.rb
CHANGED
|
@@ -34,6 +34,11 @@ module Pf2
|
|
|
34
34
|
color: "red",
|
|
35
35
|
subcategories: ["Code"],
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
name: "Native",
|
|
39
|
+
color: "blue",
|
|
40
|
+
subcategories: ["Code"],
|
|
41
|
+
},
|
|
37
42
|
{
|
|
38
43
|
name: "Native",
|
|
39
44
|
color: "lightblue",
|
|
@@ -68,7 +73,7 @@ module Pf2
|
|
|
68
73
|
def emit
|
|
69
74
|
func_table = build_func_table
|
|
70
75
|
frame_table = build_frame_table
|
|
71
|
-
stack_table = build_stack_table
|
|
76
|
+
stack_table = build_stack_table(func_table, frame_table)
|
|
72
77
|
samples = build_samples
|
|
73
78
|
|
|
74
79
|
string_table = build_string_table
|
|
@@ -173,8 +178,10 @@ module Pf2
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
@thread[:frames].each.with_index do |(id, frame), i|
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
native = (frame[:entry_type] == 'Native')
|
|
182
|
+
label = "#{native ? "Native: " : ""}#{frame[:full_label]}"
|
|
183
|
+
ret[:name] << string_id(label)
|
|
184
|
+
ret[:is_js] << !native
|
|
178
185
|
ret[:relevant_for_js] << false
|
|
179
186
|
ret[:resource] << -1
|
|
180
187
|
ret[:file_name] << nil
|
|
@@ -188,7 +195,7 @@ module Pf2
|
|
|
188
195
|
ret
|
|
189
196
|
end
|
|
190
197
|
|
|
191
|
-
def build_stack_table
|
|
198
|
+
def build_stack_table(func_table, frame_table)
|
|
192
199
|
ret = {
|
|
193
200
|
frame: [],
|
|
194
201
|
category: [],
|
|
@@ -205,7 +212,7 @@ module Pf2
|
|
|
205
212
|
|
|
206
213
|
prefix, node = queue.shift
|
|
207
214
|
ret[:frame] << @frame_id_map[node[:frame_id]]
|
|
208
|
-
ret[:category] << 1
|
|
215
|
+
ret[:category] << (build_string_table[func_table[:name][frame_table[:func][@frame_id_map[node[:frame_id]]]]].start_with?('Native:') ? 2 : 1)
|
|
209
216
|
ret[:subcategory] << nil
|
|
210
217
|
ret[:prefix] << prefix
|
|
211
218
|
|