pf2 0.2.0 → 0.4.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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -2
  3. data/Cargo.lock +186 -17
  4. data/Cargo.toml +1 -1
  5. data/README.md +18 -6
  6. data/Rakefile +8 -0
  7. data/crates/backtrace-sys2/.gitignore +1 -0
  8. data/crates/backtrace-sys2/Cargo.toml +9 -0
  9. data/crates/backtrace-sys2/build.rs +48 -0
  10. data/crates/backtrace-sys2/src/lib.rs +5 -0
  11. data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
  12. data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
  13. data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
  14. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
  15. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
  16. data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
  17. data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
  18. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
  19. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
  20. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
  21. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
  22. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
  23. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
  24. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
  25. data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
  26. data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
  27. data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
  28. data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
  29. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
  30. data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
  31. data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
  32. data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
  33. data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
  34. data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
  35. data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
  36. data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
  37. data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
  38. data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
  39. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
  40. data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
  41. data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
  42. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
  43. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
  44. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
  45. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
  46. data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
  47. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
  48. data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
  49. data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
  50. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
  51. data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
  52. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
  53. data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
  54. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
  55. data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
  56. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
  57. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
  58. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
  59. data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
  60. data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
  61. data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
  62. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
  63. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
  64. data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
  65. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
  66. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
  67. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
  68. data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
  69. data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
  70. data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
  71. data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
  72. data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
  73. data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
  74. data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
  75. data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
  76. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
  77. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
  78. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
  79. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
  80. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
  81. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
  82. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
  83. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
  84. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
  85. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
  86. data/ext/pf2/Cargo.toml +1 -0
  87. data/ext/pf2/src/backtrace.rs +127 -0
  88. data/ext/pf2/src/lib.rs +3 -0
  89. data/ext/pf2/src/profile.rs +16 -1
  90. data/ext/pf2/src/profile_serializer.rs +95 -21
  91. data/ext/pf2/src/ringbuffer.rs +7 -0
  92. data/ext/pf2/src/ruby_init.rs +18 -6
  93. data/ext/pf2/src/ruby_internal_apis.rs +47 -0
  94. data/ext/pf2/src/sample.rs +22 -1
  95. data/ext/pf2/src/signal_scheduler/configuration.rs +7 -0
  96. data/ext/pf2/src/signal_scheduler/timer_installer.rs +79 -126
  97. data/ext/pf2/src/signal_scheduler.rs +95 -26
  98. data/ext/pf2/src/timer_thread_scheduler.rs +88 -12
  99. data/ext/pf2/src/util.rs +2 -2
  100. data/lib/pf2/reporter.rb +12 -5
  101. data/lib/pf2/version.rb +1 -1
  102. data/lib/pf2.rb +3 -6
  103. metadata +97 -6
@@ -1,32 +1,28 @@
1
- use std::collections::HashMap;
1
+ use std::collections::HashSet;
2
2
  use std::ffi::c_void;
3
3
  use std::mem;
4
4
  use std::mem::ManuallyDrop;
5
5
  use std::ptr::null_mut;
6
+ use std::sync::Arc;
6
7
  use std::sync::{Mutex, RwLock};
7
- use std::{collections::HashSet, sync::Arc};
8
8
 
9
9
  use rb_sys::*;
10
10
 
11
- use crate::signal_scheduler::SignalHandlerArgs;
12
-
13
11
  use super::configuration::Configuration;
14
12
  use crate::profile::Profile;
13
+ use crate::ruby_internal_apis::rb_thread_getcpuclockid;
14
+ use crate::signal_scheduler::{cstr, SignalHandlerArgs};
15
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).
16
+ #[derive(Debug)]
20
17
  pub struct TimerInstaller {
21
- internal: Box<Mutex<Internal>>,
18
+ inner: Box<Mutex<Inner>>,
22
19
  }
23
20
 
24
- struct Internal {
21
+ #[derive(Debug)]
22
+ struct Inner {
25
23
  configuration: Configuration,
26
- target_ruby_threads: HashSet<VALUE>,
27
- registered_pthread_ids: HashSet<libc::pthread_t>,
28
- kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
29
- profile: Arc<RwLock<Profile>>,
24
+ pub profile: Arc<RwLock<Profile>>,
25
+ known_threads: HashSet<VALUE>,
30
26
  }
31
27
 
32
28
  impl TimerInstaller {
@@ -34,159 +30,116 @@ impl TimerInstaller {
34
30
  // The callback should create a timer for the thread.
35
31
  pub fn install_timer_to_ruby_threads(
36
32
  configuration: Configuration,
37
- ruby_threads: &HashSet<VALUE>,
38
33
  profile: Arc<RwLock<Profile>>,
39
- track_new_threads: bool,
40
34
  ) {
41
- let registrar = Self {
42
- internal: Box::new(Mutex::new(Internal {
43
- configuration,
44
- target_ruby_threads: ruby_threads.clone(),
45
- registered_pthread_ids: HashSet::new(),
46
- kernel_thread_id_to_ruby_thread_map: HashMap::new(),
35
+ let installer = Self {
36
+ inner: Box::new(Mutex::new(Inner {
37
+ configuration: configuration.clone(),
47
38
  profile,
39
+ known_threads: HashSet::new(),
48
40
  })),
49
41
  };
50
42
 
51
- let ptr = Box::into_raw(registrar.internal);
52
- unsafe {
53
- rb_internal_thread_add_event_hook(
54
- Some(Self::on_thread_resume),
55
- RUBY_INTERNAL_THREAD_EVENT_RESUMED,
56
- ptr as *mut c_void,
57
- );
58
- // Spawn a no-op Thread to fire the event hook
59
- // (at least 2 Ruby Threads must be active for the RESUMED hook to be fired)
60
- rb_thread_create(Some(Self::do_nothing), null_mut());
61
- };
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
+ }
62
50
 
63
- if track_new_threads {
51
+ if configuration.track_all_threads {
52
+ let ptr = Box::into_raw(installer.inner);
64
53
  unsafe {
54
+ // TODO: Clean up this hook when the profiling session ends
65
55
  rb_internal_thread_add_event_hook(
66
- Some(Self::on_thread_start),
67
- RUBY_INTERNAL_THREAD_EVENT_STARTED,
56
+ Some(Self::on_thread_resume),
57
+ RUBY_INTERNAL_THREAD_EVENT_RESUMED,
68
58
  ptr as *mut c_void,
69
59
  );
70
60
  };
71
61
  }
72
62
  }
73
63
 
74
- unsafe extern "C" fn do_nothing(_: *mut c_void) -> VALUE {
75
- Qnil.into()
76
- }
77
-
78
- // Thread resume callback
64
+ // Thread start callback
79
65
  unsafe extern "C" fn on_thread_resume(
80
66
  _flag: rb_event_flag_t,
81
67
  data: *const rb_internal_thread_event_data,
82
68
  custom_data: *mut c_void,
83
69
  ) {
84
- // The SignalScheduler (as a Ruby obj) should be passed as custom_data
85
- let internal =
86
- unsafe { ManuallyDrop::new(Box::from_raw(custom_data as *mut Mutex<Internal>)) };
87
- let mut internal = internal.lock().unwrap();
88
-
89
- // Check if the current thread is a target Ruby Thread
90
- let current_ruby_thread: VALUE = unsafe { (*data).thread };
91
- if !internal.target_ruby_threads.contains(&current_ruby_thread) {
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
- }
70
+ let ruby_thread: VALUE = unsafe { (*data).thread };
119
71
 
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();
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();
130
75
 
131
- let current_ruby_thread: VALUE = unsafe { (*data).thread };
132
- internal.target_ruby_threads.insert(current_ruby_thread);
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
+ }
133
81
  }
82
+ }
134
83
 
135
- // Creates a new POSIX timer which invocates sampling for the thread that called this function.
136
- fn register_timer_to_current_thread(
137
- configuration: &Configuration,
138
- profile: &Arc<RwLock<Profile>>,
139
- kernel_thread_id_to_ruby_thread_map: &HashMap<libc::pid_t, VALUE>,
140
- ) {
141
- let current_pthread_id = unsafe { libc::pthread_self() };
142
- let context_ruby_thread: VALUE = unsafe {
143
- *(kernel_thread_id_to_ruby_thread_map
144
- .get(&(libc::gettid()))
145
- .unwrap())
146
- };
147
-
84
+ impl Inner {
85
+ fn register_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
148
86
  // NOTE: This Box is never dropped
149
87
  let signal_handler_args = Box::new(SignalHandlerArgs {
150
- profile: Arc::clone(profile),
151
- context_ruby_thread,
88
+ profile: Arc::clone(&self.profile),
89
+ context_ruby_thread: ruby_thread,
152
90
  });
153
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
+
154
102
  // Create a signal event
155
103
  let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
156
104
  // Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
157
- // "tranpoline" the signal as any pthread can receive the signal.
105
+ // "trampoline" the signal as any pthread can receive the signal.
158
106
  sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
159
- sigevent.sigev_notify_thread_id =
160
- unsafe { libc::syscall(libc::SYS_gettid).try_into().unwrap() }; // The kernel thread ID
107
+ sigevent.sigev_notify_thread_id = kernel_thread_id;
161
108
  sigevent.sigev_signo = libc::SIGALRM;
162
109
  // Pass required args to the signal handler
163
110
  sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
164
111
 
165
- // Create and configure timer to fire every 10 ms of CPU time
112
+ // Create and configure timer to fire every _interval_ ms of CPU time
166
113
  let mut timer: libc::timer_t = unsafe { mem::zeroed() };
167
- match configuration.time_mode {
168
- crate::signal_scheduler::TimeMode::CpuTime => {
169
- let err = unsafe {
170
- libc::timer_create(libc::CLOCK_THREAD_CPUTIME_ID, &mut sigevent, &mut timer)
171
- };
172
- if err != 0 {
173
- panic!("timer_create failed: {}", err);
174
- }
175
- }
176
- crate::signal_scheduler::TimeMode::WallTime => {
177
- todo!("WallTime is not supported yet");
178
- }
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,
179
119
  };
180
- let mut its: libc::itimerspec = unsafe { mem::zeroed() };
181
- its.it_interval.tv_sec = 0;
182
- its.it_interval.tv_nsec = 10_000_000; // 10 ms
183
- its.it_value.tv_sec = 0;
184
- its.it_value.tv_nsec = 10_000_000;
185
- let err = unsafe { libc::timer_settime(timer, 0, &its, null_mut()) };
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()) };
186
126
  if err != 0 {
187
127
  panic!("timer_settime failed: {}", err);
188
128
  }
189
129
 
190
- log::debug!("timer registered for thread {}", current_pthread_id);
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
191
144
  }
192
145
  }
@@ -11,8 +11,9 @@ use crate::sample::Sample;
11
11
 
12
12
  use core::panic;
13
13
  use std::collections::HashSet;
14
- use std::ffi::{c_int, c_void, CString};
14
+ use std::ffi::{c_int, c_void, CStr, CString};
15
15
  use std::mem::ManuallyDrop;
16
+ use std::str::FromStr;
16
17
  use std::sync::{Arc, RwLock};
17
18
  use std::thread;
18
19
  use std::time::Duration;
@@ -24,7 +25,7 @@ use crate::util::*;
24
25
 
25
26
  #[derive(Debug)]
26
27
  pub struct SignalScheduler {
27
- configuration: configuration::Configuration,
28
+ configuration: Option<configuration::Configuration>,
28
29
  profile: Option<Arc<RwLock<Profile>>>,
29
30
  }
30
31
 
@@ -36,37 +37,100 @@ pub struct SignalHandlerArgs {
36
37
  impl SignalScheduler {
37
38
  fn new() -> Self {
38
39
  Self {
39
- configuration: Configuration {
40
- time_mode: TimeMode::CpuTime,
41
- },
40
+ configuration: None,
42
41
  profile: None,
43
42
  }
44
43
  }
45
44
 
46
- fn start(
47
- &mut self,
48
- _rbself: VALUE,
49
- ruby_threads_rary: VALUE,
50
- track_new_threads: VALUE,
51
- ) -> VALUE {
52
- let track_new_threads = RTEST(track_new_threads);
53
-
54
- let profile = Arc::new(RwLock::new(Profile::new()));
55
- self.start_profile_buffer_flusher_thread(&profile);
56
- self.install_signal_handler();
45
+ fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
46
+ // Parse arguments
47
+ let kwargs: VALUE = Qnil.into();
48
+ unsafe {
49
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
50
+ };
51
+ let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
52
+ unsafe {
53
+ rb_get_kwargs(
54
+ kwargs,
55
+ [
56
+ rb_intern(cstr!("interval_ms")),
57
+ rb_intern(cstr!("threads")),
58
+ rb_intern(cstr!("time_mode")),
59
+ rb_intern(cstr!("track_all_threads")),
60
+ ]
61
+ .as_mut_ptr(),
62
+ 0,
63
+ 4,
64
+ kwargs_values.as_mut_ptr(),
65
+ );
66
+ };
67
+ let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
68
+ let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
69
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
70
+ eprintln!(
71
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
72
+ interval_ms
73
+ );
74
+ 49
75
+ }))
76
+ } else {
77
+ Duration::from_millis(49)
78
+ };
79
+ let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
80
+ kwargs_values[1]
81
+ } else {
82
+ unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
83
+ };
84
+ let time_mode: configuration::TimeMode = if kwargs_values[2] != Qundef as VALUE {
85
+ let specified_mode = unsafe {
86
+ let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
87
+ let ptr = rb_string_value_ptr(&mut str);
88
+ CStr::from_ptr(ptr).to_str().unwrap()
89
+ };
90
+ configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
91
+ // Raise an ArgumentError
92
+ unsafe {
93
+ rb_raise(
94
+ rb_eArgError,
95
+ cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
96
+ )
97
+ }
98
+ })
99
+ } else {
100
+ configuration::TimeMode::CpuTime
101
+ };
102
+ let track_all_threads: bool = if kwargs_values[3] != Qundef as VALUE {
103
+ RTEST(kwargs_values[3])
104
+ } else {
105
+ false
106
+ };
57
107
 
58
108
  let mut target_ruby_threads = HashSet::new();
59
109
  unsafe {
60
- for i in 0..RARRAY_LEN(ruby_threads_rary) {
61
- let ruby_thread: VALUE = rb_ary_entry(ruby_threads_rary, i);
110
+ for i in 0..RARRAY_LEN(threads) {
111
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
62
112
  target_ruby_threads.insert(ruby_thread);
63
113
  }
64
114
  }
115
+
116
+ self.configuration = Some(Configuration {
117
+ interval,
118
+ target_ruby_threads,
119
+ time_mode,
120
+ track_all_threads,
121
+ });
122
+
123
+ Qnil.into()
124
+ }
125
+
126
+ fn start(&mut self, _rbself: VALUE) -> VALUE {
127
+ let profile = Arc::new(RwLock::new(Profile::new()));
128
+ self.start_profile_buffer_flusher_thread(&profile);
129
+ self.install_signal_handler();
130
+
65
131
  TimerInstaller::install_timer_to_ruby_threads(
66
- self.configuration.clone(),
67
- &target_ruby_threads,
132
+ self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
68
133
  Arc::clone(&profile),
69
- track_new_threads,
70
134
  );
71
135
 
72
136
  self.profile = Some(profile);
@@ -132,7 +196,7 @@ impl SignalScheduler {
132
196
  }
133
197
  };
134
198
 
135
- let sample = Sample::capture(args.context_ruby_thread); // NOT async-signal-safe
199
+ let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
136
200
  if profile.temporary_sample_buffer.push(sample).is_err() {
137
201
  log::debug!("Temporary sample buffer full. Dropping sample.");
138
202
  }
@@ -156,13 +220,18 @@ impl SignalScheduler {
156
220
 
157
221
  // Ruby Methods
158
222
 
159
- pub unsafe extern "C" fn rb_start(
223
+ pub unsafe extern "C" fn rb_initialize(
224
+ argc: c_int,
225
+ argv: *const VALUE,
160
226
  rbself: VALUE,
161
- ruby_threads: VALUE,
162
- track_new_threads: VALUE,
163
227
  ) -> VALUE {
164
228
  let mut collector = unsafe { Self::get_struct_from(rbself) };
165
- collector.start(rbself, ruby_threads, track_new_threads)
229
+ collector.initialize(argc, argv, rbself)
230
+ }
231
+
232
+ pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
233
+ let mut collector = unsafe { Self::get_struct_from(rbself) };
234
+ collector.start(rbself)
166
235
  }
167
236
 
168
237
  pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
@@ -1,6 +1,6 @@
1
1
  #![deny(unsafe_op_in_unsafe_fn)]
2
2
 
3
- use std::ffi::{c_void, CString};
3
+ use std::ffi::{c_int, c_void, CStr, CString};
4
4
  use std::mem::ManuallyDrop;
5
5
  use std::ptr::null_mut;
6
6
  use std::sync::atomic::{AtomicBool, Ordering};
@@ -18,6 +18,7 @@ use crate::util::*;
18
18
  #[derive(Clone, Debug)]
19
19
  pub struct TimerThreadScheduler {
20
20
  ruby_threads: Arc<RwLock<Vec<VALUE>>>,
21
+ interval: Option<Arc<Duration>>,
21
22
  profile: Option<Arc<RwLock<Profile>>>,
22
23
  stop_requested: Arc<AtomicBool>,
23
24
  }
@@ -32,26 +33,89 @@ impl TimerThreadScheduler {
32
33
  fn new() -> Self {
33
34
  TimerThreadScheduler {
34
35
  ruby_threads: Arc::new(RwLock::new(vec![])),
36
+ interval: None,
35
37
  profile: None,
36
38
  stop_requested: Arc::new(AtomicBool::new(false)),
37
39
  }
38
40
  }
39
41
 
40
- fn start(&mut self, _rbself: VALUE, ruby_threads: VALUE) -> VALUE {
41
- // Register threads
42
- let stored_threads = &mut self.ruby_threads.try_write().unwrap();
42
+ fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
43
+ // Parse arguments
44
+ let kwargs: VALUE = Qnil.into();
43
45
  unsafe {
44
- for i in 0..RARRAY_LEN(ruby_threads) {
45
- stored_threads.push(rb_ary_entry(ruby_threads, i));
46
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
47
+ };
48
+ let mut kwargs_values: [VALUE; 3] = [Qnil.into(); 3];
49
+ unsafe {
50
+ rb_get_kwargs(
51
+ kwargs,
52
+ [
53
+ rb_intern(cstr!("interval_ms")),
54
+ rb_intern(cstr!("threads")),
55
+ rb_intern(cstr!("time_mode")),
56
+ ]
57
+ .as_mut_ptr(),
58
+ 0,
59
+ 3,
60
+ kwargs_values.as_mut_ptr(),
61
+ );
62
+ };
63
+ let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
64
+ let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
65
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
66
+ eprintln!(
67
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
68
+ interval_ms
69
+ );
70
+ 49
71
+ }))
72
+ } else {
73
+ Duration::from_millis(49)
74
+ };
75
+ let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
76
+ kwargs_values[1]
77
+ } else {
78
+ unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
79
+ };
80
+ if kwargs_values[2] != Qundef as VALUE {
81
+ let specified_mode = unsafe {
82
+ let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
83
+ let ptr = rb_string_value_ptr(&mut str);
84
+ CStr::from_ptr(ptr).to_str().unwrap()
85
+ };
86
+ if specified_mode != "wall" {
87
+ // Raise an ArgumentError
88
+ unsafe {
89
+ rb_raise(
90
+ rb_eArgError,
91
+ cstr!("TimerThreadScheduler only supports :wall mode."),
92
+ )
93
+ }
46
94
  }
47
95
  }
48
96
 
97
+ let mut target_ruby_threads = Vec::new();
98
+ unsafe {
99
+ for i in 0..RARRAY_LEN(threads) {
100
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
101
+ target_ruby_threads.push(ruby_thread);
102
+ }
103
+ }
104
+
105
+ self.interval = Some(Arc::new(interval));
106
+ self.ruby_threads = Arc::new(RwLock::new(target_ruby_threads.into_iter().collect()));
107
+
108
+ Qnil.into()
109
+ }
110
+
111
+ fn start(&mut self, _rbself: VALUE) -> VALUE {
49
112
  // Create Profile
50
113
  let profile = Arc::new(RwLock::new(Profile::new()));
51
114
  self.start_profile_buffer_flusher_thread(&profile);
52
115
 
53
116
  // Start monitoring thread
54
117
  let stop_requested = Arc::clone(&self.stop_requested);
118
+ let interval = Arc::clone(self.interval.as_ref().unwrap());
55
119
  let postponed_job_args: Box<PostponedJobArgs> = Box::new(PostponedJobArgs {
56
120
  ruby_threads: Arc::clone(&self.ruby_threads),
57
121
  profile: Arc::clone(&profile),
@@ -63,7 +127,9 @@ impl TimerThreadScheduler {
63
127
  Box::into_raw(postponed_job_args) as *mut c_void, // FIXME: leak
64
128
  )
65
129
  };
66
- thread::spawn(move || Self::thread_main_loop(stop_requested, postponed_job_handle));
130
+ thread::spawn(move || {
131
+ Self::thread_main_loop(stop_requested, interval, postponed_job_handle)
132
+ });
67
133
 
68
134
  self.profile = Some(profile);
69
135
 
@@ -72,6 +138,7 @@ impl TimerThreadScheduler {
72
138
 
73
139
  fn thread_main_loop(
74
140
  stop_requested: Arc<AtomicBool>,
141
+ interval: Arc<Duration>,
75
142
  postponed_job_handle: rb_postponed_job_handle_t,
76
143
  ) {
77
144
  loop {
@@ -81,8 +148,8 @@ impl TimerThreadScheduler {
81
148
  unsafe {
82
149
  rb_postponed_job_trigger(postponed_job_handle);
83
150
  }
84
- // sleep for 50 ms
85
- thread::sleep(Duration::from_millis(50));
151
+
152
+ thread::sleep(*interval);
86
153
  }
87
154
  }
88
155
 
@@ -136,7 +203,7 @@ impl TimerThreadScheduler {
136
203
  continue;
137
204
  }
138
205
 
139
- let sample = Sample::capture(*ruby_thread);
206
+ let sample = Sample::capture(*ruby_thread, &profile.backtrace_state);
140
207
  if profile.temporary_sample_buffer.push(sample).is_err() {
141
208
  log::debug!("Temporary sample buffer full. Dropping sample.");
142
209
  }
@@ -164,10 +231,19 @@ impl TimerThreadScheduler {
164
231
 
165
232
  // Ruby Methods
166
233
 
234
+ pub unsafe extern "C" fn rb_initialize(
235
+ argc: c_int,
236
+ argv: *const VALUE,
237
+ rbself: VALUE,
238
+ ) -> VALUE {
239
+ let mut collector = Self::get_struct_from(rbself);
240
+ collector.initialize(argc, argv, rbself)
241
+ }
242
+
167
243
  // SampleCollector.start
168
- pub unsafe extern "C" fn rb_start(rbself: VALUE, ruby_threads: VALUE, _: VALUE) -> VALUE {
244
+ pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
169
245
  let mut collector = Self::get_struct_from(rbself);
170
- collector.start(rbself, ruby_threads)
246
+ collector.start(rbself)
171
247
  }
172
248
 
173
249
  // SampleCollector.stop
data/ext/pf2/src/util.rs CHANGED
@@ -13,10 +13,10 @@ pub(crate) use cstr;
13
13
  pub type RubyCFunc = unsafe extern "C" fn() -> VALUE;
14
14
 
15
15
  // TODO: rewrite as macro
16
- pub fn to_ruby_cfunc1<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
16
+ pub fn to_ruby_cfunc_with_no_args<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
17
17
  unsafe { transmute::<unsafe extern "C" fn(T) -> VALUE, RubyCFunc>(f) }
18
18
  }
19
- pub fn to_ruby_cfunc3<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
19
+ pub fn to_ruby_cfunc_with_args<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
20
20
  unsafe { transmute::<unsafe extern "C" fn(T, U, V) -> VALUE, RubyCFunc>(f) }
21
21
  }
22
22
 
data/lib/pf2/reporter.rb CHANGED
@@ -34,6 +34,11 @@ module Pf2
34
34
  color: "red",
35
35
  subcategories: ["Code"],
36
36
  },
37
+ {
38
+ name: "Native",
39
+ color: "blue",
40
+ subcategories: ["Code"],
41
+ },
37
42
  {
38
43
  name: "Native",
39
44
  color: "lightblue",
@@ -68,7 +73,7 @@ module Pf2
68
73
  def emit
69
74
  func_table = build_func_table
70
75
  frame_table = build_frame_table
71
- stack_table = build_stack_table
76
+ stack_table = build_stack_table(func_table, frame_table)
72
77
  samples = build_samples
73
78
 
74
79
  string_table = build_string_table
@@ -173,8 +178,10 @@ module Pf2
173
178
  }
174
179
 
175
180
  @thread[:frames].each.with_index do |(id, frame), i|
176
- ret[:name] << string_id(frame[:full_label])
177
- ret[:is_js] << false
181
+ native = (frame[:entry_type] == 'Native')
182
+ label = "#{native ? "Native: " : ""}#{frame[:full_label]}"
183
+ ret[:name] << string_id(label)
184
+ ret[:is_js] << !native
178
185
  ret[:relevant_for_js] << false
179
186
  ret[:resource] << -1
180
187
  ret[:file_name] << nil
@@ -188,7 +195,7 @@ module Pf2
188
195
  ret
189
196
  end
190
197
 
191
- def build_stack_table
198
+ def build_stack_table(func_table, frame_table)
192
199
  ret = {
193
200
  frame: [],
194
201
  category: [],
@@ -205,7 +212,7 @@ module Pf2
205
212
 
206
213
  prefix, node = queue.shift
207
214
  ret[:frame] << @frame_id_map[node[:frame_id]]
208
- ret[:category] << 1
215
+ ret[:category] << (build_string_table[func_table[:name][frame_table[:func][@frame_id_map[node[:frame_id]]]]].start_with?('Native:') ? 2 : 1)
209
216
  ret[:subcategory] << nil
210
217
  ret[:prefix] << prefix
211
218