pf2 0.3.0 → 0.5.0

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.3.0'
2
+ VERSION = '0.5.0'
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,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pf2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-02-05 00:00:00.000000000 Z
10
+ date: 2024-03-24 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake-compiler
@@ -38,6 +37,20 @@ dependencies:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
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'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: minitest
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -52,7 +65,6 @@ dependencies:
52
65
  - - ">="
53
66
  - !ruby/object:Gem::Version
54
67
  version: '0'
55
- description:
56
68
  email:
57
69
  - osyoyu@osyoyu.com
58
70
  executables:
@@ -156,16 +168,22 @@ files:
156
168
  - ext/pf2/src/profile_serializer.rs
157
169
  - ext/pf2/src/ringbuffer.rs
158
170
  - ext/pf2/src/ruby_init.rs
171
+ - ext/pf2/src/ruby_internal_apis.rs
159
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
160
178
  - ext/pf2/src/siginfo_t.c
161
179
  - ext/pf2/src/signal_scheduler.rs
162
- - ext/pf2/src/signal_scheduler/configuration.rs
163
- - ext/pf2/src/signal_scheduler/timer_installer.rs
164
180
  - ext/pf2/src/timer_thread_scheduler.rs
165
181
  - ext/pf2/src/util.rs
166
182
  - lib/pf2.rb
167
183
  - lib/pf2/cli.rb
168
184
  - lib/pf2/reporter.rb
185
+ - lib/pf2/serve.rb
186
+ - lib/pf2/session.rb
169
187
  - lib/pf2/version.rb
170
188
  homepage: https://github.com/osyoyu/pf2
171
189
  licenses:
@@ -175,7 +193,6 @@ metadata:
175
193
  homepage_uri: https://github.com/osyoyu/pf2
176
194
  source_code_uri: https://github.com/osyoyu/pf2
177
195
  changelog_uri: https://github.com/osyoyu/pf2/blob/master/CHANGELOG.md
178
- post_install_message:
179
196
  rdoc_options: []
180
197
  require_paths:
181
198
  - lib
@@ -191,7 +208,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
208
  version: '0'
192
209
  requirements: []
193
210
  rubygems_version: 3.6.0.dev
194
- signing_key:
195
211
  specification_version: 4
196
212
  summary: Yet another Ruby profiler
197
213
  test_files: []
@@ -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_new_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,199 +0,0 @@
1
- use std::collections::HashMap;
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::{Mutex, RwLock};
7
- use std::{collections::HashSet, sync::Arc};
8
-
9
- use rb_sys::*;
10
-
11
- use crate::signal_scheduler::SignalHandlerArgs;
12
-
13
- use super::configuration::Configuration;
14
- use crate::profile::Profile;
15
-
16
- // We could avoid deferring the timer creation by combining pthread_getcpuclockid(3) and timer_create(2) here,
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).
20
- pub struct TimerInstaller {
21
- internal: Box<Mutex<Internal>>,
22
- }
23
-
24
- struct Internal {
25
- configuration: Configuration,
26
- registered_pthread_ids: HashSet<libc::pthread_t>,
27
- kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
28
- profile: Arc<RwLock<Profile>>,
29
- }
30
-
31
- impl TimerInstaller {
32
- // Register a callback that gets called when a Ruby Thread is resumed.
33
- // The callback should create a timer for the thread.
34
- pub fn install_timer_to_ruby_threads(
35
- configuration: Configuration,
36
- profile: Arc<RwLock<Profile>>,
37
- ) {
38
- let registrar = Self {
39
- internal: Box::new(Mutex::new(Internal {
40
- configuration: configuration.clone(),
41
- registered_pthread_ids: HashSet::new(),
42
- kernel_thread_id_to_ruby_thread_map: HashMap::new(),
43
- profile,
44
- })),
45
- };
46
-
47
- let ptr = Box::into_raw(registrar.internal);
48
- unsafe {
49
- rb_internal_thread_add_event_hook(
50
- Some(Self::on_thread_resume),
51
- RUBY_INTERNAL_THREAD_EVENT_RESUMED,
52
- ptr as *mut c_void,
53
- );
54
- // Spawn a no-op Thread to fire the event hook
55
- // (at least 2 Ruby Threads must be active for the RESUMED hook to be fired)
56
- rb_thread_create(Some(Self::do_nothing), null_mut());
57
- };
58
-
59
- if configuration.track_new_threads {
60
- unsafe {
61
- rb_internal_thread_add_event_hook(
62
- Some(Self::on_thread_start),
63
- RUBY_INTERNAL_THREAD_EVENT_STARTED,
64
- ptr as *mut c_void,
65
- );
66
- };
67
- }
68
- }
69
-
70
- unsafe extern "C" fn do_nothing(_: *mut c_void) -> VALUE {
71
- Qnil.into()
72
- }
73
-
74
- // Thread resume callback
75
- unsafe extern "C" fn on_thread_resume(
76
- _flag: rb_event_flag_t,
77
- data: *const rb_internal_thread_event_data,
78
- custom_data: *mut c_void,
79
- ) {
80
- // The SignalScheduler (as a Ruby obj) should be passed as custom_data
81
- let internal =
82
- unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Internal>)) };
83
- let mut internal = internal.lock().unwrap();
84
-
85
- // Check if the current thread is a target Ruby Thread
86
- let current_ruby_thread: VALUE = unsafe { (*data).thread };
87
- if !internal
88
- .configuration
89
- .target_ruby_threads
90
- .contains(&current_ruby_thread)
91
- {
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(&current_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
- }
119
-
120
- // Thread resume callback
121
- unsafe extern "C" fn on_thread_start(
122
- _flag: rb_event_flag_t,
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();
130
-
131
- let current_ruby_thread: VALUE = unsafe { (*data).thread };
132
- internal
133
- .configuration
134
- .target_ruby_threads
135
- .insert(current_ruby_thread);
136
- }
137
-
138
- // Creates a new POSIX timer which invocates sampling for the thread that called this function.
139
- fn register_timer_to_current_thread(
140
- configuration: &Configuration,
141
- profile: &Arc<RwLock<Profile>>,
142
- kernel_thread_id_to_ruby_thread_map: &HashMap<libc::pid_t, VALUE>,
143
- ) {
144
- let current_pthread_id = unsafe { libc::pthread_self() };
145
- let context_ruby_thread: VALUE = unsafe {
146
- *(kernel_thread_id_to_ruby_thread_map
147
- .get(&(libc::gettid()))
148
- .unwrap())
149
- };
150
-
151
- // NOTE: This Box is never dropped
152
- let signal_handler_args = Box::new(SignalHandlerArgs {
153
- profile: Arc::clone(profile),
154
- context_ruby_thread,
155
- });
156
-
157
- // Create a signal event
158
- let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
159
- // Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
160
- // "tranpoline" the signal as any pthread can receive the signal.
161
- sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
162
- sigevent.sigev_notify_thread_id =
163
- unsafe { libc::syscall(libc::SYS_gettid).try_into().unwrap() }; // The kernel thread ID
164
- sigevent.sigev_signo = libc::SIGALRM;
165
- // Pass required args to the signal handler
166
- sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
167
-
168
- // Create and configure timer to fire every _interval_ ms of CPU time
169
- let mut timer: libc::timer_t = unsafe { mem::zeroed() };
170
- let clockid = match configuration.time_mode {
171
- crate::signal_scheduler::TimeMode::CpuTime => libc::CLOCK_THREAD_CPUTIME_ID,
172
- crate::signal_scheduler::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
173
- };
174
- let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
175
- if err != 0 {
176
- panic!("timer_create failed: {}", err);
177
- }
178
- let itimerspec = Self::duration_to_itimerspec(&configuration.interval);
179
- let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
180
- if err != 0 {
181
- panic!("timer_settime failed: {}", err);
182
- }
183
-
184
- log::debug!("timer registered for thread {}", current_pthread_id);
185
- }
186
-
187
- fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
188
- let nanos = duration.as_nanos();
189
- let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
190
- let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
191
-
192
- let mut its: libc::itimerspec = unsafe { mem::zeroed() };
193
- its.it_interval.tv_sec = seconds_part;
194
- its.it_interval.tv_nsec = nanos_part;
195
- its.it_value.tv_sec = seconds_part;
196
- its.it_value.tv_nsec = nanos_part;
197
- its
198
- }
199
- }