Skip to content

Commit dd36c58

Browse files
authored
Merge pull request #16 from osyoyu/session
Introduce Pf2::Session and hide Schedulers from Ruby code
2 parents 711bb5d + 09ead2e commit dd36c58

13 files changed

Lines changed: 529 additions & 446 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
- `pf2 serve` subcommand
66
- `pf2 serve -- ruby target.rb`
77
- Profile programs without any change
8+
- Introduce `Pf2::Session` (https://github.com/osyoyu/pf2/pull/16)
9+
- `Session` will be responsible for managing Profiles and Schedulers
10+
11+
### Removed
12+
13+
- `Pf2::SignalScheduler` and `Pf2::TimerThreadScheduler` are now hidden from Ruby.
814

915

1016
## [0.4.0] - 2024-03-22

ext/pf2/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ mod profile;
99
mod profile_serializer;
1010
mod ringbuffer;
1111
mod sample;
12+
mod scheduler;
13+
mod session;
1214
#[cfg(target_os = "linux")]
1315
mod signal_scheduler;
1416
mod timer_thread_scheduler;

ext/pf2/src/ruby_init.rs

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
use rb_sys::*;
44

5-
#[cfg(target_os = "linux")]
6-
use crate::signal_scheduler::SignalScheduler;
7-
use crate::timer_thread_scheduler::TimerThreadScheduler;
5+
use crate::session::ruby_object::SessionRubyObject;
86
use crate::util::*;
97

108
#[allow(non_snake_case)]
@@ -21,53 +19,24 @@ extern "C" fn Init_pf2() {
2119
unsafe {
2220
let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
2321

24-
#[cfg(target_os = "linux")]
25-
{
26-
let rb_mPf2_SignalScheduler =
27-
rb_define_class_under(rb_mPf2, cstr!("SignalScheduler"), rb_cObject);
28-
rb_define_alloc_func(rb_mPf2_SignalScheduler, Some(SignalScheduler::rb_alloc));
29-
rb_define_method(
30-
rb_mPf2_SignalScheduler,
31-
cstr!("initialize"),
32-
Some(to_ruby_cfunc_with_args(SignalScheduler::rb_initialize)),
33-
-1,
34-
);
35-
rb_define_method(
36-
rb_mPf2_SignalScheduler,
37-
cstr!("start"),
38-
Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_start)),
39-
0,
40-
);
41-
rb_define_method(
42-
rb_mPf2_SignalScheduler,
43-
cstr!("stop"),
44-
Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_stop)),
45-
0,
46-
);
47-
}
48-
49-
let rb_mPf2_TimerThreadScheduler =
50-
rb_define_class_under(rb_mPf2, cstr!("TimerThreadScheduler"), rb_cObject);
51-
rb_define_alloc_func(
52-
rb_mPf2_TimerThreadScheduler,
53-
Some(TimerThreadScheduler::rb_alloc),
54-
);
22+
let rb_mPf2_Session = rb_define_class_under(rb_mPf2, cstr!("Session"), rb_cObject);
23+
rb_define_alloc_func(rb_mPf2_Session, Some(SessionRubyObject::rb_alloc));
5524
rb_define_method(
56-
rb_mPf2_TimerThreadScheduler,
25+
rb_mPf2_Session,
5726
cstr!("initialize"),
58-
Some(to_ruby_cfunc_with_args(TimerThreadScheduler::rb_initialize)),
27+
Some(to_ruby_cfunc_with_args(SessionRubyObject::rb_initialize)),
5928
-1,
6029
);
6130
rb_define_method(
62-
rb_mPf2_TimerThreadScheduler,
31+
rb_mPf2_Session,
6332
cstr!("start"),
64-
Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_start)),
33+
Some(to_ruby_cfunc_with_no_args(SessionRubyObject::rb_start)),
6534
0,
6635
);
6736
rb_define_method(
68-
rb_mPf2_TimerThreadScheduler,
37+
rb_mPf2_Session,
6938
cstr!("stop"),
70-
Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_stop)),
39+
Some(to_ruby_cfunc_with_no_args(SessionRubyObject::rb_stop)),
7140
0,
7241
);
7342
}

ext/pf2/src/scheduler.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use rb_sys::{size_t, VALUE};
2+
3+
pub trait Scheduler {
4+
fn start(&mut self) -> VALUE;
5+
fn stop(&mut self) -> VALUE;
6+
fn dmark(&self);
7+
fn dfree(&self);
8+
fn dsize(&self) -> size_t;
9+
}

ext/pf2/src/session.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
pub mod configuration;
2+
pub mod ruby_object;
3+
4+
use std::collections::HashSet;
5+
use std::ffi::{c_int, CStr};
6+
use std::str::FromStr as _;
7+
use std::time::Duration;
8+
9+
use rb_sys::*;
10+
11+
use self::configuration::Configuration;
12+
use crate::scheduler::Scheduler;
13+
use crate::signal_scheduler::SignalScheduler;
14+
use crate::timer_thread_scheduler::TimerThreadScheduler;
15+
use crate::util::*;
16+
17+
pub struct Session {
18+
pub configuration: Configuration,
19+
pub scheduler: Box<dyn Scheduler>,
20+
}
21+
22+
impl Session {
23+
pub fn new_from_rb_initialize(argc: c_int, argv: *const VALUE, rbself: VALUE) -> Self {
24+
// Parse arguments
25+
let kwargs: VALUE = Qnil.into();
26+
unsafe {
27+
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
28+
};
29+
let mut kwargs_values: [VALUE; 5] = [Qnil.into(); 5];
30+
unsafe {
31+
rb_get_kwargs(
32+
kwargs,
33+
[
34+
rb_intern(cstr!("interval_ms")),
35+
rb_intern(cstr!("threads")),
36+
rb_intern(cstr!("time_mode")),
37+
rb_intern(cstr!("track_all_threads")),
38+
rb_intern(cstr!("scheduler")),
39+
]
40+
.as_mut_ptr(),
41+
0,
42+
5,
43+
kwargs_values.as_mut_ptr(),
44+
);
45+
};
46+
47+
let interval = Self::parse_option_interval_ms(kwargs_values[0]);
48+
let threads = Self::parse_option_threads(kwargs_values[1]);
49+
let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
50+
let track_all_threads = Self::parse_option_track_all_threads(kwargs_values[3]);
51+
let scheduler = Self::parse_option_scheduler(kwargs_values[4]);
52+
53+
let configuration = Configuration {
54+
scheduler,
55+
interval,
56+
target_ruby_threads: threads.clone(),
57+
time_mode,
58+
track_all_threads,
59+
};
60+
61+
// Store configuration as a Ruby Hash for convenience
62+
unsafe {
63+
rb_iv_set(rbself, cstr!("@configuration"), configuration.to_rb_hash());
64+
}
65+
66+
let scheduler: Box<dyn Scheduler> = match configuration.scheduler {
67+
configuration::Scheduler::Signal => Box::new(SignalScheduler::new(&configuration)),
68+
configuration::Scheduler::TimerThread => {
69+
Box::new(TimerThreadScheduler::new(&configuration))
70+
}
71+
};
72+
73+
Session {
74+
configuration,
75+
scheduler,
76+
}
77+
}
78+
79+
fn parse_option_interval_ms(value: VALUE) -> Duration {
80+
if value == Qundef as VALUE {
81+
// Return default
82+
return configuration::DEFAULT_INTERVAL;
83+
}
84+
85+
let interval_ms = unsafe { rb_num2long(value) };
86+
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
87+
eprintln!(
88+
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
89+
interval_ms
90+
);
91+
49
92+
}))
93+
}
94+
95+
fn parse_option_threads(value: VALUE) -> HashSet<VALUE> {
96+
let threads = if value == Qundef as VALUE {
97+
// Use Thread.list (all active Threads)
98+
unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
99+
} else {
100+
value
101+
};
102+
103+
let mut set: HashSet<VALUE> = HashSet::new();
104+
unsafe {
105+
for i in 0..RARRAY_LEN(threads) {
106+
set.insert(rb_ary_entry(threads, i));
107+
}
108+
}
109+
set
110+
}
111+
112+
fn parse_option_time_mode(value: VALUE) -> configuration::TimeMode {
113+
if value == Qundef as VALUE {
114+
// Return default
115+
return configuration::DEFAULT_TIME_MODE;
116+
}
117+
118+
let specified_mode = unsafe {
119+
let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
120+
let ptr = rb_string_value_ptr(&mut str);
121+
CStr::from_ptr(ptr).to_str().unwrap()
122+
};
123+
configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
124+
// Raise an ArgumentError if the mode is invalid
125+
unsafe {
126+
rb_raise(
127+
rb_eArgError,
128+
cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
129+
)
130+
}
131+
})
132+
}
133+
134+
fn parse_option_track_all_threads(value: VALUE) -> bool {
135+
if value == Qundef as VALUE {
136+
// Return default
137+
return false;
138+
}
139+
todo!("Implement track_all_threads");
140+
}
141+
142+
fn parse_option_scheduler(value: VALUE) -> configuration::Scheduler {
143+
if value == Qundef as VALUE {
144+
// Return default
145+
return configuration::DEFAULT_SCHEDULER;
146+
}
147+
148+
let specified_scheduler = unsafe {
149+
let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
150+
let ptr = rb_string_value_ptr(&mut str);
151+
CStr::from_ptr(ptr).to_str().unwrap()
152+
};
153+
configuration::Scheduler::from_str(specified_scheduler).unwrap_or_else(|_| {
154+
// Raise an ArgumentError if the mode is invalid
155+
unsafe {
156+
rb_raise(
157+
rb_eArgError,
158+
cstr!("Invalid scheduler. Valid values are ':signal' and ':timer_thread'."),
159+
)
160+
}
161+
})
162+
}
163+
164+
pub fn start(&mut self) -> VALUE {
165+
self.scheduler.start()
166+
}
167+
168+
pub fn stop(&mut self) -> VALUE {
169+
self.scheduler.stop()
170+
}
171+
172+
pub fn dmark(&self) {
173+
self.scheduler.dmark()
174+
}
175+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::collections::HashSet;
2+
use std::str::FromStr;
3+
use std::time::Duration;
4+
5+
use rb_sys::*;
6+
7+
use crate::util::cstr;
8+
9+
pub const DEFAULT_SCHEDULER: Scheduler = Scheduler::Signal;
10+
pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(49);
11+
pub const DEFAULT_TIME_MODE: TimeMode = TimeMode::CpuTime;
12+
13+
#[derive(Clone, Debug)]
14+
pub struct Configuration {
15+
pub scheduler: Scheduler,
16+
pub interval: Duration,
17+
pub time_mode: TimeMode,
18+
pub target_ruby_threads: HashSet<VALUE>,
19+
pub track_all_threads: bool,
20+
}
21+
22+
#[derive(Clone, Debug)]
23+
pub enum Scheduler {
24+
Signal,
25+
TimerThread,
26+
}
27+
28+
impl FromStr for Scheduler {
29+
type Err = ();
30+
31+
fn from_str(s: &str) -> Result<Self, Self::Err> {
32+
match s {
33+
"signal" => Ok(Self::Signal),
34+
"timer_thread" => Ok(Self::TimerThread),
35+
_ => Err(()),
36+
}
37+
}
38+
}
39+
40+
#[derive(Clone, Debug)]
41+
pub enum TimeMode {
42+
CpuTime,
43+
WallTime,
44+
}
45+
46+
impl FromStr for TimeMode {
47+
type Err = ();
48+
49+
fn from_str(s: &str) -> Result<Self, Self::Err> {
50+
match s {
51+
"cpu" => Ok(Self::CpuTime),
52+
"wall" => Ok(Self::WallTime),
53+
_ => Err(()),
54+
}
55+
}
56+
}
57+
58+
impl Configuration {
59+
pub fn to_rb_hash(&self) -> VALUE {
60+
let hash: VALUE = unsafe { rb_hash_new() };
61+
unsafe {
62+
rb_hash_aset(
63+
hash,
64+
rb_id2sym(rb_intern(cstr!("scheduler"))),
65+
rb_id2sym(rb_intern(match self.scheduler {
66+
Scheduler::Signal => cstr!("signal"),
67+
Scheduler::TimerThread => cstr!("timer_thread"),
68+
})),
69+
);
70+
rb_hash_aset(
71+
hash,
72+
rb_id2sym(rb_intern(cstr!("interval_ms"))),
73+
rb_int2inum(self.interval.as_millis().try_into().unwrap()),
74+
);
75+
rb_hash_aset(
76+
hash,
77+
rb_id2sym(rb_intern(cstr!("time_mode"))),
78+
rb_id2sym(rb_intern(match self.time_mode {
79+
TimeMode::CpuTime => cstr!("cpu"),
80+
TimeMode::WallTime => cstr!("wall"),
81+
})),
82+
);
83+
}
84+
hash
85+
}
86+
}

0 commit comments

Comments
 (0)