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.
@@ -0,0 +1,7 @@
1
+ module Pf2
2
+ class Session
3
+ attr_reader :configuration
4
+
5
+ # Implementation is in Rust code.
6
+ end
7
+ end
data/lib/pf2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pf2
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.1'
3
3
  end
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
- @@default_scheduler = default_scheduler_class.new(...)
18
- @@default_scheduler.start
9
+ @@session = Pf2::Session.new(...)
10
+ @@session.start
19
11
  end
20
12
 
21
- def self.stop(...)
22
- @@default_scheduler.stop(...)
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([Thread.current], true)
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.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-22 00:00:00.000000000 Z
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/signal_scheduler/configuration.rs
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
- }