pf2 0.1.0 → 0.3.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -2
  3. data/Cargo.lock +650 -0
  4. data/Cargo.toml +3 -0
  5. data/README.md +110 -13
  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 +25 -0
  87. data/ext/pf2/build.rs +3 -0
  88. data/ext/pf2/extconf.rb +6 -1
  89. data/ext/pf2/src/backtrace.rs +126 -0
  90. data/ext/pf2/src/lib.rs +15 -0
  91. data/ext/pf2/src/profile.rs +65 -0
  92. data/ext/pf2/src/profile_serializer.rs +204 -0
  93. data/ext/pf2/src/ringbuffer.rs +152 -0
  94. data/ext/pf2/src/ruby_init.rs +74 -0
  95. data/ext/pf2/src/sample.rs +66 -0
  96. data/ext/pf2/src/siginfo_t.c +5 -0
  97. data/ext/pf2/src/signal_scheduler/configuration.rs +31 -0
  98. data/ext/pf2/src/signal_scheduler/timer_installer.rs +199 -0
  99. data/ext/pf2/src/signal_scheduler.rs +311 -0
  100. data/ext/pf2/src/timer_thread_scheduler.rs +319 -0
  101. data/ext/pf2/src/util.rs +30 -0
  102. data/lib/pf2/cli.rb +1 -1
  103. data/lib/pf2/reporter.rb +48 -16
  104. data/lib/pf2/version.rb +1 -1
  105. data/lib/pf2.rb +20 -5
  106. metadata +128 -5
  107. data/ext/pf2/pf2.c +0 -246
@@ -0,0 +1,199 @@
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
+ }
@@ -0,0 +1,311 @@
1
+ #![deny(unsafe_op_in_unsafe_fn)]
2
+
3
+ mod configuration;
4
+ mod timer_installer;
5
+
6
+ use self::configuration::{Configuration, TimeMode};
7
+ use self::timer_installer::TimerInstaller;
8
+ use crate::profile::Profile;
9
+ use crate::profile_serializer::ProfileSerializer;
10
+ use crate::sample::Sample;
11
+
12
+ use core::panic;
13
+ use std::collections::HashSet;
14
+ use std::ffi::{c_int, c_void, CStr, CString};
15
+ use std::mem::ManuallyDrop;
16
+ use std::str::FromStr;
17
+ use std::sync::{Arc, RwLock};
18
+ use std::thread;
19
+ use std::time::Duration;
20
+ use std::{mem, ptr::null_mut};
21
+
22
+ use rb_sys::*;
23
+
24
+ use crate::util::*;
25
+
26
+ #[derive(Debug)]
27
+ pub struct SignalScheduler {
28
+ configuration: Option<configuration::Configuration>,
29
+ profile: Option<Arc<RwLock<Profile>>>,
30
+ }
31
+
32
+ pub struct SignalHandlerArgs {
33
+ profile: Arc<RwLock<Profile>>,
34
+ context_ruby_thread: VALUE,
35
+ }
36
+
37
+ impl SignalScheduler {
38
+ fn new() -> Self {
39
+ Self {
40
+ configuration: None,
41
+ profile: None,
42
+ }
43
+ }
44
+
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_new_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_new_threads: bool = if kwargs_values[3] != Qundef as VALUE {
103
+ RTEST(kwargs_values[3])
104
+ } else {
105
+ false
106
+ };
107
+
108
+ let mut target_ruby_threads = HashSet::new();
109
+ unsafe {
110
+ for i in 0..RARRAY_LEN(threads) {
111
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
112
+ target_ruby_threads.insert(ruby_thread);
113
+ }
114
+ }
115
+
116
+ self.configuration = Some(Configuration {
117
+ interval,
118
+ target_ruby_threads,
119
+ time_mode,
120
+ track_new_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
+
131
+ TimerInstaller::install_timer_to_ruby_threads(
132
+ self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
133
+ Arc::clone(&profile),
134
+ );
135
+
136
+ self.profile = Some(profile);
137
+
138
+ Qtrue.into()
139
+ }
140
+
141
+ fn stop(&mut self, _rbself: VALUE) -> VALUE {
142
+ if let Some(profile) = &self.profile {
143
+ // Finalize
144
+ match profile.try_write() {
145
+ Ok(mut profile) => {
146
+ profile.flush_temporary_sample_buffer();
147
+ }
148
+ Err(_) => {
149
+ println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
150
+ return Qfalse.into();
151
+ }
152
+ }
153
+
154
+ let profile = profile.try_read().unwrap();
155
+ log::debug!("Number of samples: {}", profile.samples.len());
156
+
157
+ let serialized = ProfileSerializer::serialize(&profile);
158
+ let serialized = CString::new(serialized).unwrap();
159
+ unsafe { rb_str_new_cstr(serialized.as_ptr()) }
160
+ } else {
161
+ panic!("stop() called before start()");
162
+ }
163
+ }
164
+
165
+ // Install signal handler for profiling events to the current process.
166
+ fn install_signal_handler(&self) {
167
+ let mut sa: libc::sigaction = unsafe { mem::zeroed() };
168
+ sa.sa_sigaction = Self::signal_handler as usize;
169
+ sa.sa_flags = libc::SA_SIGINFO;
170
+ let err = unsafe { libc::sigaction(libc::SIGALRM, &sa, null_mut()) };
171
+ if err != 0 {
172
+ panic!("sigaction failed: {}", err);
173
+ }
174
+ }
175
+
176
+ // Respond to the signal and collect a sample.
177
+ // This function is called when a timer fires.
178
+ //
179
+ // Expected to be async-signal-safe, but the current implementation is not.
180
+ extern "C" fn signal_handler(
181
+ _sig: c_int,
182
+ info: *mut libc::siginfo_t,
183
+ _ucontext: *mut libc::ucontext_t,
184
+ ) {
185
+ let args = unsafe {
186
+ let ptr = extract_si_value_sival_ptr(info) as *mut SignalHandlerArgs;
187
+ ManuallyDrop::new(Box::from_raw(ptr))
188
+ };
189
+
190
+ let mut profile = match args.profile.try_write() {
191
+ Ok(profile) => profile,
192
+ Err(_) => {
193
+ // FIXME: Do we want to properly collect GC samples? I don't know yet.
194
+ log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
195
+ return;
196
+ }
197
+ };
198
+
199
+ let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
200
+ if profile.temporary_sample_buffer.push(sample).is_err() {
201
+ log::debug!("Temporary sample buffer full. Dropping sample.");
202
+ }
203
+ }
204
+
205
+ fn start_profile_buffer_flusher_thread(&self, profile: &Arc<RwLock<Profile>>) {
206
+ let profile = Arc::clone(profile);
207
+ thread::spawn(move || loop {
208
+ log::trace!("Flushing temporary sample buffer");
209
+ match profile.try_write() {
210
+ Ok(mut profile) => {
211
+ profile.flush_temporary_sample_buffer();
212
+ }
213
+ Err(_) => {
214
+ log::debug!("flusher: Failed to acquire profile lock");
215
+ }
216
+ }
217
+ thread::sleep(Duration::from_millis(500));
218
+ });
219
+ }
220
+
221
+ // Ruby Methods
222
+
223
+ pub unsafe extern "C" fn rb_initialize(
224
+ argc: c_int,
225
+ argv: *const VALUE,
226
+ rbself: VALUE,
227
+ ) -> VALUE {
228
+ let mut collector = unsafe { Self::get_struct_from(rbself) };
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)
235
+ }
236
+
237
+ pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {
238
+ let mut collector = unsafe { Self::get_struct_from(rbself) };
239
+ collector.stop(rbself)
240
+ }
241
+
242
+ // Functions for TypedData
243
+
244
+ // Extract the SignalScheduler struct from a Ruby object
245
+ unsafe fn get_struct_from(obj: VALUE) -> ManuallyDrop<Box<Self>> {
246
+ unsafe {
247
+ let ptr = rb_check_typeddata(obj, &RBDATA);
248
+ ManuallyDrop::new(Box::from_raw(ptr as *mut SignalScheduler))
249
+ }
250
+ }
251
+
252
+ #[allow(non_snake_case)]
253
+ pub unsafe extern "C" fn rb_alloc(_rbself: VALUE) -> VALUE {
254
+ let collector = Box::new(SignalScheduler::new());
255
+ unsafe { Arc::increment_strong_count(&collector) };
256
+
257
+ unsafe {
258
+ let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
259
+ let rb_cSignalScheduler =
260
+ rb_define_class_under(rb_mPf2, cstr!("SignalScheduler"), rb_cObject);
261
+
262
+ // "Wrap" the SignalScheduler struct into a Ruby object
263
+ rb_data_typed_object_wrap(
264
+ rb_cSignalScheduler,
265
+ Box::into_raw(collector) as *mut c_void,
266
+ &RBDATA,
267
+ )
268
+ }
269
+ }
270
+
271
+ unsafe extern "C" fn dmark(ptr: *mut c_void) {
272
+ unsafe {
273
+ let collector = ManuallyDrop::new(Box::from_raw(ptr as *mut SignalScheduler));
274
+ if let Some(profile) = &collector.profile {
275
+ match profile.read() {
276
+ Ok(profile) => {
277
+ profile.dmark();
278
+ }
279
+ Err(_) => {
280
+ panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ unsafe extern "C" fn dfree(ptr: *mut c_void) {
288
+ unsafe {
289
+ drop(Box::from_raw(ptr as *mut SignalScheduler));
290
+ }
291
+ }
292
+
293
+ unsafe extern "C" fn dsize(_: *const c_void) -> size_t {
294
+ // FIXME: Report something better
295
+ mem::size_of::<SignalScheduler>() as size_t
296
+ }
297
+ }
298
+
299
+ static mut RBDATA: rb_data_type_t = rb_data_type_t {
300
+ wrap_struct_name: cstr!("SignalScheduler"),
301
+ function: rb_data_type_struct__bindgen_ty_1 {
302
+ dmark: Some(SignalScheduler::dmark),
303
+ dfree: Some(SignalScheduler::dfree),
304
+ dsize: Some(SignalScheduler::dsize),
305
+ dcompact: None,
306
+ reserved: [null_mut(); 1],
307
+ },
308
+ parent: null_mut(),
309
+ data: null_mut(),
310
+ flags: 0,
311
+ };