pf2 0.1.0 → 0.3.0

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