pf2 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- }