pf2 0.3.0 → 0.5.0

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.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
- }