pf2 0.4.0 → 0.5.1
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 -0
- data/Cargo.lock +2 -2
- data/README.md +15 -3
- data/ext/pf2/src/lib.rs +4 -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 +32 -2
- 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 +242 -0
- data/ext/pf2/src/signal_scheduler.rs +105 -221
- data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +39 -0
- 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 +24 -4
- data/ext/pf2/src/signal_scheduler/configuration.rs +0 -31
- data/ext/pf2/src/signal_scheduler/timer_installer.rs +0 -145
data/lib/pf2/session.rb
ADDED
data/lib/pf2/version.rb
CHANGED
data/lib/pf2.rb
CHANGED
@@ -1,31 +1,24 @@
|
|
1
1
|
require_relative 'pf2/pf2'
|
2
|
+
require_relative 'pf2/session'
|
2
3
|
require_relative 'pf2/version'
|
3
4
|
|
4
5
|
module Pf2
|
5
6
|
class Error < StandardError; end
|
6
7
|
|
7
|
-
def self.default_scheduler_class
|
8
|
-
# SignalScheduler is Linux-only. Use TimerThreadScheduler on other platforms.
|
9
|
-
if defined?(SignalScheduler)
|
10
|
-
SignalScheduler
|
11
|
-
else
|
12
|
-
TimerThreadScheduler
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
def self.start(...)
|
17
|
-
@@
|
18
|
-
@@
|
9
|
+
@@session = Pf2::Session.new(...)
|
10
|
+
@@session.start
|
19
11
|
end
|
20
12
|
|
21
|
-
def self.stop
|
22
|
-
@@
|
13
|
+
def self.stop
|
14
|
+
@@session.stop
|
23
15
|
end
|
24
16
|
|
25
17
|
def self.profile(&block)
|
26
18
|
raise ArgumentError, "block required" unless block_given?
|
27
|
-
start(
|
19
|
+
start(threads: Thread.list)
|
28
20
|
yield
|
29
21
|
stop
|
22
|
+
@@session = nil # let GC clean up the session
|
30
23
|
end
|
31
24
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pf2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daisuke Aritomo
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2024-03-
|
10
|
+
date: 2024-03-24 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rake-compiler
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: 0.9.63
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: webrick
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: minitest
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,15 +170,21 @@ files:
|
|
156
170
|
- ext/pf2/src/ruby_init.rs
|
157
171
|
- ext/pf2/src/ruby_internal_apis.rs
|
158
172
|
- ext/pf2/src/sample.rs
|
173
|
+
- ext/pf2/src/scheduler.rs
|
174
|
+
- ext/pf2/src/session.rs
|
175
|
+
- ext/pf2/src/session/configuration.rs
|
176
|
+
- ext/pf2/src/session/new_thread_watcher.rs
|
177
|
+
- ext/pf2/src/session/ruby_object.rs
|
159
178
|
- ext/pf2/src/siginfo_t.c
|
160
179
|
- ext/pf2/src/signal_scheduler.rs
|
161
|
-
- ext/pf2/src/
|
162
|
-
- ext/pf2/src/signal_scheduler/timer_installer.rs
|
180
|
+
- ext/pf2/src/signal_scheduler_unsupported_platform.rs
|
163
181
|
- ext/pf2/src/timer_thread_scheduler.rs
|
164
182
|
- ext/pf2/src/util.rs
|
165
183
|
- lib/pf2.rb
|
166
184
|
- lib/pf2/cli.rb
|
167
185
|
- lib/pf2/reporter.rb
|
186
|
+
- lib/pf2/serve.rb
|
187
|
+
- lib/pf2/session.rb
|
168
188
|
- lib/pf2/version.rb
|
169
189
|
homepage: https://github.com/osyoyu/pf2
|
170
190
|
licenses:
|
@@ -1,31 +0,0 @@
|
|
1
|
-
use std::collections::HashSet;
|
2
|
-
use std::str::FromStr;
|
3
|
-
use std::time::Duration;
|
4
|
-
|
5
|
-
use rb_sys::VALUE;
|
6
|
-
|
7
|
-
#[derive(Clone, Debug)]
|
8
|
-
pub struct Configuration {
|
9
|
-
pub interval: Duration,
|
10
|
-
pub time_mode: TimeMode,
|
11
|
-
pub target_ruby_threads: HashSet<VALUE>,
|
12
|
-
pub track_all_threads: bool,
|
13
|
-
}
|
14
|
-
|
15
|
-
#[derive(Clone, Debug)]
|
16
|
-
pub enum TimeMode {
|
17
|
-
CpuTime,
|
18
|
-
WallTime,
|
19
|
-
}
|
20
|
-
|
21
|
-
impl FromStr for TimeMode {
|
22
|
-
type Err = ();
|
23
|
-
|
24
|
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
25
|
-
match s {
|
26
|
-
"cpu" => Ok(Self::CpuTime),
|
27
|
-
"wall" => Ok(Self::WallTime),
|
28
|
-
_ => Err(()),
|
29
|
-
}
|
30
|
-
}
|
31
|
-
}
|
@@ -1,145 +0,0 @@
|
|
1
|
-
use std::collections::HashSet;
|
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::Arc;
|
7
|
-
use std::sync::{Mutex, RwLock};
|
8
|
-
|
9
|
-
use rb_sys::*;
|
10
|
-
|
11
|
-
use super::configuration::Configuration;
|
12
|
-
use crate::profile::Profile;
|
13
|
-
use crate::ruby_internal_apis::rb_thread_getcpuclockid;
|
14
|
-
use crate::signal_scheduler::{cstr, SignalHandlerArgs};
|
15
|
-
|
16
|
-
#[derive(Debug)]
|
17
|
-
pub struct TimerInstaller {
|
18
|
-
inner: Box<Mutex<Inner>>,
|
19
|
-
}
|
20
|
-
|
21
|
-
#[derive(Debug)]
|
22
|
-
struct Inner {
|
23
|
-
configuration: Configuration,
|
24
|
-
pub profile: Arc<RwLock<Profile>>,
|
25
|
-
known_threads: HashSet<VALUE>,
|
26
|
-
}
|
27
|
-
|
28
|
-
impl TimerInstaller {
|
29
|
-
// Register a callback that gets called when a Ruby Thread is resumed.
|
30
|
-
// The callback should create a timer for the thread.
|
31
|
-
pub fn install_timer_to_ruby_threads(
|
32
|
-
configuration: Configuration,
|
33
|
-
profile: Arc<RwLock<Profile>>,
|
34
|
-
) {
|
35
|
-
let installer = Self {
|
36
|
-
inner: Box::new(Mutex::new(Inner {
|
37
|
-
configuration: configuration.clone(),
|
38
|
-
profile,
|
39
|
-
known_threads: HashSet::new(),
|
40
|
-
})),
|
41
|
-
};
|
42
|
-
|
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
|
-
}
|
50
|
-
|
51
|
-
if configuration.track_all_threads {
|
52
|
-
let ptr = Box::into_raw(installer.inner);
|
53
|
-
unsafe {
|
54
|
-
// TODO: Clean up this hook when the profiling session ends
|
55
|
-
rb_internal_thread_add_event_hook(
|
56
|
-
Some(Self::on_thread_resume),
|
57
|
-
RUBY_INTERNAL_THREAD_EVENT_RESUMED,
|
58
|
-
ptr as *mut c_void,
|
59
|
-
);
|
60
|
-
};
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
// Thread start callback
|
65
|
-
unsafe extern "C" fn on_thread_resume(
|
66
|
-
_flag: rb_event_flag_t,
|
67
|
-
data: *const rb_internal_thread_event_data,
|
68
|
-
custom_data: *mut c_void,
|
69
|
-
) {
|
70
|
-
let ruby_thread: VALUE = unsafe { (*data).thread };
|
71
|
-
|
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();
|
75
|
-
|
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
|
-
}
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
impl Inner {
|
85
|
-
fn register_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
|
86
|
-
// NOTE: This Box is never dropped
|
87
|
-
let signal_handler_args = Box::new(SignalHandlerArgs {
|
88
|
-
profile: Arc::clone(&self.profile),
|
89
|
-
context_ruby_thread: ruby_thread,
|
90
|
-
});
|
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
|
-
|
102
|
-
// Create a signal event
|
103
|
-
let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
|
104
|
-
// Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
|
105
|
-
// "trampoline" the signal as any pthread can receive the signal.
|
106
|
-
sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
|
107
|
-
sigevent.sigev_notify_thread_id = kernel_thread_id;
|
108
|
-
sigevent.sigev_signo = libc::SIGALRM;
|
109
|
-
// Pass required args to the signal handler
|
110
|
-
sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
|
111
|
-
|
112
|
-
// Create and configure timer to fire every _interval_ ms of CPU time
|
113
|
-
let mut timer: libc::timer_t = unsafe { mem::zeroed() };
|
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,
|
119
|
-
};
|
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()) };
|
126
|
-
if err != 0 {
|
127
|
-
panic!("timer_settime failed: {}", err);
|
128
|
-
}
|
129
|
-
|
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
|
144
|
-
}
|
145
|
-
}
|