pf2 0.9.0 → 0.11.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/.document +3 -0
  3. data/.rdoc_options +6 -0
  4. data/CHANGELOG.md +36 -0
  5. data/README.md +31 -4
  6. data/Rakefile +8 -9
  7. data/doc/development.md +6 -0
  8. data/ext/pf2/debug.h +12 -0
  9. data/ext/pf2/extconf.rb +23 -6
  10. data/ext/pf2/pf2.c +17 -0
  11. data/ext/{pf2c → pf2}/sample.c +7 -3
  12. data/ext/pf2/sample.h +27 -0
  13. data/ext/{pf2c → pf2}/serializer.c +1 -1
  14. data/ext/{pf2c → pf2}/session.c +116 -31
  15. data/ext/{pf2c → pf2}/session.h +7 -2
  16. data/lib/pf2/cli.rb +3 -11
  17. data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
  18. data/lib/pf2/reporter/stack_weaver.rb +9 -1
  19. data/lib/pf2/reporter.rb +0 -1
  20. data/lib/pf2/version.rb +1 -1
  21. data/lib/pf2.rb +1 -2
  22. metadata +41 -129
  23. data/Cargo.lock +0 -630
  24. data/Cargo.toml +0 -3
  25. data/crates/backtrace-sys2/.gitignore +0 -1
  26. data/crates/backtrace-sys2/Cargo.toml +0 -9
  27. data/crates/backtrace-sys2/build.rs +0 -45
  28. data/crates/backtrace-sys2/src/lib.rs +0 -5
  29. data/crates/backtrace-sys2/src/libbacktrace/.gitignore +0 -15
  30. data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +0 -9286
  31. data/crates/backtrace-sys2/src/libbacktrace/LICENSE +0 -29
  32. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +0 -708
  33. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +0 -2820
  34. data/crates/backtrace-sys2/src/libbacktrace/README.md +0 -46
  35. data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +0 -864
  36. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +0 -167
  37. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +0 -136
  38. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +0 -104
  39. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +0 -113
  40. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +0 -66
  41. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +0 -129
  42. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +0 -189
  43. data/crates/backtrace-sys2/src/libbacktrace/btest.c +0 -517
  44. data/crates/backtrace-sys2/src/libbacktrace/compile +0 -348
  45. data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +0 -38
  46. data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +0 -31
  47. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +0 -7545
  48. data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +0 -369
  49. data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +0 -123
  50. data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +0 -23
  51. data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +0 -98
  52. data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +0 -68
  53. data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +0 -117
  54. data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +0 -37
  55. data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +0 -227
  56. data/crates/backtrace-sys2/src/libbacktrace/config.guess +0 -1700
  57. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +0 -185
  58. data/crates/backtrace-sys2/src/libbacktrace/config.sub +0 -1885
  59. data/crates/backtrace-sys2/src/libbacktrace/configure +0 -15929
  60. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +0 -632
  61. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +0 -4409
  62. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +0 -120
  63. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +0 -43
  64. data/crates/backtrace-sys2/src/libbacktrace/elf.c +0 -7465
  65. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +0 -407
  66. data/crates/backtrace-sys2/src/libbacktrace/filenames.h +0 -52
  67. data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +0 -13
  68. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +0 -65
  69. data/crates/backtrace-sys2/src/libbacktrace/install-sh +0 -501
  70. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +0 -114
  71. data/crates/backtrace-sys2/src/libbacktrace/internal.h +0 -428
  72. data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +0 -8636
  73. data/crates/backtrace-sys2/src/libbacktrace/macho.c +0 -1361
  74. data/crates/backtrace-sys2/src/libbacktrace/missing +0 -215
  75. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +0 -331
  76. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +0 -110
  77. data/crates/backtrace-sys2/src/libbacktrace/move-if-change +0 -83
  78. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +0 -410
  79. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +0 -66
  80. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +0 -1123
  81. data/crates/backtrace-sys2/src/libbacktrace/posix.c +0 -104
  82. data/crates/backtrace-sys2/src/libbacktrace/print.c +0 -117
  83. data/crates/backtrace-sys2/src/libbacktrace/read.c +0 -110
  84. data/crates/backtrace-sys2/src/libbacktrace/simple.c +0 -108
  85. data/crates/backtrace-sys2/src/libbacktrace/sort.c +0 -108
  86. data/crates/backtrace-sys2/src/libbacktrace/state.c +0 -72
  87. data/crates/backtrace-sys2/src/libbacktrace/stest.c +0 -137
  88. data/crates/backtrace-sys2/src/libbacktrace/test-driver +0 -148
  89. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +0 -55
  90. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +0 -234
  91. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +0 -110
  92. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +0 -161
  93. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +0 -92
  94. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +0 -65
  95. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +0 -1617
  96. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +0 -508
  97. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +0 -523
  98. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +0 -541
  99. data/ext/pf2/Cargo.toml +0 -25
  100. data/ext/pf2/build.rs +0 -10
  101. data/ext/pf2/src/backtrace.rs +0 -127
  102. data/ext/pf2/src/lib.rs +0 -22
  103. data/ext/pf2/src/profile.rs +0 -69
  104. data/ext/pf2/src/profile_serializer.rs +0 -241
  105. data/ext/pf2/src/ringbuffer.rs +0 -150
  106. data/ext/pf2/src/ruby_c_api_helper.c +0 -6
  107. data/ext/pf2/src/ruby_init.rs +0 -40
  108. data/ext/pf2/src/ruby_internal_apis.rs +0 -77
  109. data/ext/pf2/src/sample.rs +0 -67
  110. data/ext/pf2/src/scheduler.rs +0 -10
  111. data/ext/pf2/src/serialization/profile.rs +0 -48
  112. data/ext/pf2/src/serialization/serializer.rs +0 -329
  113. data/ext/pf2/src/serialization.rs +0 -2
  114. data/ext/pf2/src/session/configuration.rs +0 -114
  115. data/ext/pf2/src/session/new_thread_watcher.rs +0 -80
  116. data/ext/pf2/src/session/ruby_object.rs +0 -90
  117. data/ext/pf2/src/session.rs +0 -248
  118. data/ext/pf2/src/siginfo_t.c +0 -5
  119. data/ext/pf2/src/signal_scheduler.rs +0 -201
  120. data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +0 -39
  121. data/ext/pf2/src/timer_thread_scheduler.rs +0 -179
  122. data/ext/pf2/src/util.rs +0 -31
  123. data/ext/pf2c/extconf.rb +0 -21
  124. data/ext/pf2c/pf2.c +0 -17
  125. data/ext/pf2c/sample.h +0 -22
  126. data/lib/pf2/reporter/firefox_profiler.rb +0 -397
  127. data/lib/pf2/session.rb +0 -9
  128. data/rust-toolchain.toml +0 -2
  129. data/rustfmt.toml +0 -1
  130. /data/ext/{pf2c → pf2}/backtrace_state.c +0 -0
  131. /data/ext/{pf2c → pf2}/backtrace_state.h +0 -0
  132. /data/ext/{pf2c → pf2}/configuration.c +0 -0
  133. /data/ext/{pf2c → pf2}/configuration.h +0 -0
  134. /data/ext/{pf2c → pf2}/pf2.h +0 -0
  135. /data/ext/{pf2c → pf2}/ringbuffer.c +0 -0
  136. /data/ext/{pf2c → pf2}/ringbuffer.h +0 -0
  137. /data/ext/{pf2c → pf2}/serializer.h +0 -0
@@ -1,248 +0,0 @@
1
- pub mod configuration;
2
- mod new_thread_watcher;
3
- pub mod ruby_object;
4
-
5
- use std::collections::HashSet;
6
- use std::ffi::{c_int, CStr, CString};
7
- use std::str::FromStr as _;
8
- use std::sync::atomic::{AtomicBool, Ordering};
9
- use std::sync::{Arc, RwLock};
10
- use std::thread;
11
- use std::time::Duration;
12
-
13
- use rb_sys::*;
14
-
15
- use self::configuration::Configuration;
16
- use self::new_thread_watcher::NewThreadWatcher;
17
- use crate::profile::Profile;
18
- use crate::scheduler::Scheduler;
19
- #[cfg(target_os = "linux")]
20
- use crate::signal_scheduler::SignalScheduler;
21
- #[cfg(not(target_os = "linux"))]
22
- use crate::signal_scheduler_unsupported_platform::SignalScheduler;
23
- use crate::timer_thread_scheduler::TimerThreadScheduler;
24
- use crate::util::*;
25
-
26
- pub struct Session {
27
- pub configuration: Configuration,
28
- pub scheduler: Arc<dyn Scheduler>,
29
- pub profile: Arc<RwLock<Profile>>,
30
- pub running: Arc<AtomicBool>,
31
- pub new_thread_watcher: Option<NewThreadWatcher>,
32
- }
33
-
34
- impl Session {
35
- pub fn new_from_rb_initialize(argc: c_int, argv: *const VALUE, rbself: VALUE) -> Self {
36
- // Parse arguments
37
- let kwargs: VALUE = Qnil.into();
38
- unsafe {
39
- rb_scan_args(argc, argv, cstr!(":"), &kwargs);
40
- };
41
- let mut kwargs_values: [VALUE; 5] = [Qnil.into(); 5];
42
- unsafe {
43
- rb_get_kwargs(
44
- kwargs,
45
- [
46
- rb_intern(cstr!("interval_ms")),
47
- rb_intern(cstr!("threads")),
48
- rb_intern(cstr!("time_mode")),
49
- rb_intern(cstr!("scheduler")),
50
- rb_intern(cstr!("use_experimental_serializer")),
51
- ]
52
- .as_mut_ptr(),
53
- 0,
54
- 5,
55
- kwargs_values.as_mut_ptr(),
56
- );
57
- };
58
-
59
- let interval = Self::parse_option_interval_ms(kwargs_values[0]);
60
- let threads = Self::parse_option_threads(kwargs_values[1]);
61
- let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
62
- let scheduler = Self::parse_option_scheduler(kwargs_values[3]);
63
- let use_experimental_serializer =
64
- Self::parse_option_use_experimental_serializer(kwargs_values[4]);
65
-
66
- let configuration = Configuration {
67
- scheduler,
68
- interval,
69
- target_ruby_threads: threads.clone(),
70
- time_mode,
71
- use_experimental_serializer,
72
- };
73
-
74
- match configuration.validate() {
75
- Ok(_) => {}
76
- Err(msg) => unsafe {
77
- rb_raise(rb_eArgError, CString::new(msg).unwrap().as_c_str().as_ptr());
78
- },
79
- };
80
-
81
- // Store configuration as a Ruby Hash for convenience
82
- unsafe {
83
- rb_iv_set(rbself, cstr!("@configuration"), configuration.to_rb_hash());
84
- }
85
-
86
- // Create a new Profile
87
- let profile = Arc::new(RwLock::new(Profile::new()));
88
-
89
- // Initialize the specified Scheduler
90
- let scheduler: Arc<dyn Scheduler> = match configuration.scheduler {
91
- configuration::Scheduler::Signal => {
92
- Arc::new(SignalScheduler::new(&configuration, Arc::clone(&profile)))
93
- }
94
- configuration::Scheduler::TimerThread => {
95
- Arc::new(TimerThreadScheduler::new(&configuration, Arc::clone(&profile)))
96
- }
97
- };
98
-
99
- let running = Arc::new(AtomicBool::new(false));
100
-
101
- let new_thread_watcher = match threads {
102
- configuration::Threads::All => {
103
- let scheduler = Arc::clone(&scheduler);
104
- let running = Arc::clone(&running);
105
- Some(NewThreadWatcher::watch(move |thread: VALUE| {
106
- if running.load(Ordering::Relaxed) {
107
- log::debug!("New Ruby thread detected: {:?}", thread);
108
- scheduler.on_new_thread(thread);
109
- }
110
- }))
111
- }
112
- configuration::Threads::Targeted(_) => None,
113
- };
114
-
115
- Session { configuration, scheduler, profile, running, new_thread_watcher }
116
- }
117
-
118
- fn parse_option_interval_ms(value: VALUE) -> Duration {
119
- if value == Qundef as VALUE {
120
- // Return default
121
- return configuration::DEFAULT_INTERVAL;
122
- }
123
-
124
- let interval_ms = unsafe { rb_num2long(value) };
125
- Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
126
- eprintln!(
127
- "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (9ms).",
128
- interval_ms
129
- );
130
- 9
131
- }))
132
- }
133
-
134
- fn parse_option_threads(value: VALUE) -> configuration::Threads {
135
- if (value == Qundef as VALUE)
136
- || (value == Qnil as VALUE)
137
- || (value == unsafe { rb_id2sym(rb_intern(cstr!("all"))) })
138
- {
139
- return configuration::Threads::All;
140
- }
141
-
142
- let mut set: HashSet<VALUE> = HashSet::new();
143
- unsafe {
144
- for i in 0..RARRAY_LEN(value) {
145
- set.insert(rb_ary_entry(value, i));
146
- }
147
- }
148
- configuration::Threads::Targeted(set)
149
- }
150
-
151
- fn parse_option_time_mode(value: VALUE) -> configuration::TimeMode {
152
- if value == Qundef as VALUE {
153
- // Return default
154
- return configuration::DEFAULT_TIME_MODE;
155
- }
156
-
157
- let specified_mode = unsafe {
158
- let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
159
- let ptr = rb_string_value_ptr(&mut str);
160
- CStr::from_ptr(ptr).to_str().unwrap()
161
- };
162
- configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
163
- // Raise an ArgumentError if the mode is invalid
164
- unsafe {
165
- rb_raise(
166
- rb_eArgError,
167
- cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
168
- )
169
- }
170
- })
171
- }
172
-
173
- fn parse_option_scheduler(value: VALUE) -> configuration::Scheduler {
174
- if value == Qundef as VALUE {
175
- // Return default
176
- return configuration::DEFAULT_SCHEDULER;
177
- }
178
-
179
- let specified_scheduler = unsafe {
180
- let mut str = rb_funcall(value, rb_intern(cstr!("to_s")), 0);
181
- let ptr = rb_string_value_ptr(&mut str);
182
- CStr::from_ptr(ptr).to_str().unwrap()
183
- };
184
- let scheduler =
185
- configuration::Scheduler::from_str(specified_scheduler).unwrap_or_else(|_| {
186
- // Raise an ArgumentError if the mode is invalid
187
- unsafe {
188
- rb_raise(
189
- rb_eArgError,
190
- cstr!("Invalid scheduler. Valid values are ':signal' and ':timer_thread'."),
191
- )
192
- }
193
- });
194
-
195
- // Raise an ArgumentError if the scheduler is not supported on the current platform
196
- if !cfg!(target_os = "linux") && scheduler == configuration::Scheduler::Signal {
197
- unsafe {
198
- rb_raise(rb_eArgError, cstr!("Signal scheduler is not supported on this platform."))
199
- }
200
- }
201
- scheduler
202
- }
203
-
204
- fn parse_option_use_experimental_serializer(value: VALUE) -> bool {
205
- if value == Qundef as VALUE {
206
- return false;
207
- }
208
- RTEST(value)
209
- }
210
-
211
- pub fn start(&mut self) -> VALUE {
212
- self.running.store(true, Ordering::Relaxed);
213
- self.start_profile_buffer_flusher_thread();
214
- self.scheduler.start()
215
- }
216
-
217
- fn start_profile_buffer_flusher_thread(&self) {
218
- let profile = Arc::clone(&self.profile);
219
- let running = Arc::clone(&self.running);
220
- log::debug!("flusher: Starting");
221
- thread::spawn(move || loop {
222
- if !running.load(Ordering::Relaxed) {
223
- log::debug!("flusher: Exiting");
224
- break;
225
- }
226
-
227
- log::trace!("flusher: Flushing temporary sample buffer");
228
- match profile.try_write() {
229
- Ok(mut profile) => {
230
- profile.flush_temporary_sample_buffer();
231
- }
232
- Err(_) => {
233
- log::debug!("flusher: Failed to acquire profile lock");
234
- }
235
- }
236
- thread::sleep(Duration::from_millis(500));
237
- });
238
- }
239
-
240
- pub fn stop(&mut self) -> VALUE {
241
- self.running.store(false, Ordering::Relaxed);
242
- self.scheduler.stop()
243
- }
244
-
245
- pub fn dmark(&self) {
246
- self.scheduler.dmark()
247
- }
248
- }
@@ -1,5 +0,0 @@
1
- #include <signal.h>
2
-
3
- void *extract_si_value_sival_ptr(siginfo_t *siginfo) {
4
- return siginfo->si_value.sival_ptr;
5
- }
@@ -1,201 +0,0 @@
1
- #![deny(unsafe_op_in_unsafe_fn)]
2
-
3
- use crate::profile::Profile;
4
- use crate::profile_serializer::ProfileSerializer;
5
- use crate::ruby_internal_apis::rb_thread_getcpuclockid;
6
- use crate::sample::Sample;
7
- use crate::scheduler::Scheduler;
8
- use crate::serialization::serializer::ProfileSerializer2;
9
- use crate::session::configuration::{self, Configuration};
10
-
11
- use core::panic;
12
- use std::ffi::{c_int, c_void, CString};
13
- use std::mem::ManuallyDrop;
14
- use std::sync::{Arc, RwLock};
15
- use std::{mem, ptr::null_mut};
16
-
17
- use rb_sys::*;
18
-
19
- use crate::util::*;
20
-
21
- #[derive(Debug)]
22
- pub struct SignalScheduler {
23
- configuration: Configuration,
24
- profile: Arc<RwLock<Profile>>,
25
- }
26
-
27
- pub struct SignalHandlerArgs {
28
- profile: Arc<RwLock<Profile>>,
29
- context_ruby_thread: VALUE,
30
- }
31
-
32
- impl Scheduler for SignalScheduler {
33
- fn start(&self) -> VALUE {
34
- self.install_signal_handler();
35
-
36
- if let configuration::Threads::Targeted(threads) = &self.configuration.target_ruby_threads {
37
- for ruby_thread in threads.iter() {
38
- self.install_timer_to_ruby_thread(*ruby_thread);
39
- }
40
- }
41
-
42
- Qtrue.into()
43
- }
44
-
45
- fn stop(&self) -> VALUE {
46
- // Finalize
47
- match self.profile.try_write() {
48
- Ok(mut profile) => {
49
- profile.flush_temporary_sample_buffer();
50
- profile.end_instant = Some(std::time::Instant::now());
51
- }
52
- Err(_) => {
53
- println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
54
- return Qfalse.into();
55
- }
56
- }
57
-
58
- let profile = self.profile.try_read().unwrap();
59
- log::debug!("Number of samples: {}", profile.samples.len());
60
-
61
- if self.configuration.use_experimental_serializer {
62
- let mut ser = ProfileSerializer2::new();
63
- ser.serialize(&profile);
64
- ser.to_ruby_hash()
65
- } else {
66
- let serialized = ProfileSerializer::serialize(&profile);
67
- let string = CString::new(serialized).unwrap();
68
- unsafe { rb_str_new_cstr(string.as_ptr()) }
69
- }
70
- }
71
-
72
- fn on_new_thread(&self, thread: VALUE) {
73
- self.install_timer_to_ruby_thread(thread);
74
- }
75
-
76
- fn dmark(&self) {
77
- match self.profile.read() {
78
- Ok(profile) => unsafe {
79
- profile.dmark();
80
- },
81
- Err(_) => {
82
- panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
83
- }
84
- }
85
- }
86
-
87
- fn dfree(&self) {
88
- // No-op
89
- }
90
-
91
- fn dsize(&self) -> size_t {
92
- // FIXME: Report something better
93
- mem::size_of::<Self>() as size_t
94
- }
95
- }
96
-
97
- impl SignalScheduler {
98
- pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
99
- Self { configuration: configuration.clone(), profile }
100
- }
101
-
102
- // Install signal handler for profiling events to the current process.
103
- fn install_signal_handler(&self) {
104
- let mut sa: libc::sigaction = unsafe { mem::zeroed() };
105
- sa.sa_sigaction = Self::signal_handler as usize;
106
- sa.sa_flags = libc::SA_SIGINFO | libc::SA_RESTART;
107
- let err = unsafe { libc::sigaction(libc::SIGALRM, &sa, null_mut()) };
108
- if err != 0 {
109
- panic!("sigaction failed: {}", err);
110
- }
111
- log::debug!("Signal handler installed");
112
- }
113
-
114
- // Respond to the signal and collect a sample.
115
- // This function is called when a timer fires.
116
- //
117
- // Expected to be async-signal-safe, but the current implementation is not.
118
- extern "C" fn signal_handler(
119
- _sig: c_int,
120
- info: *mut libc::siginfo_t,
121
- _ucontext: *mut libc::ucontext_t,
122
- ) {
123
- let args = unsafe {
124
- let ptr = extract_si_value_sival_ptr(info) as *mut SignalHandlerArgs;
125
- ManuallyDrop::new(Box::from_raw(ptr))
126
- };
127
-
128
- let mut profile = match args.profile.try_write() {
129
- Ok(profile) => profile,
130
- Err(_) => {
131
- // FIXME: Do we want to properly collect GC samples? I don't know yet.
132
- log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
133
- return;
134
- }
135
- };
136
-
137
- let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
138
- if profile.temporary_sample_buffer.push(sample).is_err() {
139
- log::debug!("Temporary sample buffer full. Dropping sample.");
140
- }
141
- }
142
-
143
- fn install_timer_to_ruby_thread(&self, ruby_thread: VALUE) {
144
- // NOTE: This Box never gets dropped
145
- let signal_handler_args = Box::new(SignalHandlerArgs {
146
- profile: Arc::clone(&self.profile),
147
- context_ruby_thread: ruby_thread,
148
- });
149
-
150
- // rb_funcall deadlocks when called within a THREAD_EVENT_STARTED hook
151
- let kernel_thread_id: i32 = i32::try_from(unsafe {
152
- rb_num2int(rb_funcall(
153
- ruby_thread,
154
- rb_intern(cstr!("native_thread_id")), // kernel thread ID
155
- 0,
156
- ))
157
- })
158
- .unwrap();
159
-
160
- // Create a signal event
161
- let mut sigevent: libc::sigevent = unsafe { mem::zeroed() };
162
- // Note: SIGEV_THREAD_ID is Linux-specific. In other platforms, we would need to
163
- // "trampoline" the signal as any pthread can receive the signal.
164
- sigevent.sigev_notify = libc::SIGEV_THREAD_ID;
165
- sigevent.sigev_notify_thread_id = kernel_thread_id;
166
- sigevent.sigev_signo = libc::SIGALRM;
167
- // Pass required args to the signal handler
168
- sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
169
-
170
- // Create and configure timer to fire every _interval_ ms of CPU time
171
- let mut timer: libc::timer_t = unsafe { mem::zeroed() };
172
- let clockid = match self.configuration.time_mode {
173
- configuration::TimeMode::CpuTime => unsafe { rb_thread_getcpuclockid(ruby_thread) },
174
- configuration::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
175
- };
176
- let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
177
- if err != 0 {
178
- panic!("timer_create failed: {}", err);
179
- }
180
- let itimerspec = Self::duration_to_itimerspec(&self.configuration.interval);
181
- let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
182
- if err != 0 {
183
- panic!("timer_settime failed: {}", err);
184
- }
185
-
186
- log::debug!("timer registered for thread {}", ruby_thread);
187
- }
188
-
189
- fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
190
- let nanos = duration.as_nanos();
191
- let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
192
- let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
193
-
194
- let mut its: libc::itimerspec = unsafe { mem::zeroed() };
195
- its.it_interval.tv_sec = seconds_part;
196
- its.it_interval.tv_nsec = nanos_part;
197
- its.it_value.tv_sec = seconds_part;
198
- its.it_value.tv_nsec = nanos_part;
199
- its
200
- }
201
- }
@@ -1,39 +0,0 @@
1
- use std::sync::{Arc, RwLock};
2
-
3
- use crate::profile::Profile;
4
- use crate::scheduler::Scheduler;
5
- use crate::session::configuration::Configuration;
6
-
7
- pub struct SignalScheduler {}
8
-
9
- impl Scheduler for SignalScheduler {
10
- fn start(&self) -> rb_sys::VALUE {
11
- unimplemented!()
12
- }
13
-
14
- fn stop(&self) -> rb_sys::VALUE {
15
- unimplemented!()
16
- }
17
-
18
- fn on_new_thread(&self, thread: rb_sys::VALUE) {
19
- unimplemented!()
20
- }
21
-
22
- fn dmark(&self) {
23
- unimplemented!()
24
- }
25
-
26
- fn dfree(&self) {
27
- unimplemented!()
28
- }
29
-
30
- fn dsize(&self) -> rb_sys::size_t {
31
- unimplemented!()
32
- }
33
- }
34
-
35
- impl SignalScheduler {
36
- pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
37
- unimplemented!()
38
- }
39
- }
@@ -1,179 +0,0 @@
1
- #![deny(unsafe_op_in_unsafe_fn)]
2
-
3
- use std::ffi::{c_void, CString};
4
- use std::mem::ManuallyDrop;
5
- use std::sync::atomic::{AtomicBool, Ordering};
6
- use std::sync::{Arc, RwLock};
7
- use std::thread;
8
-
9
- use rb_sys::*;
10
-
11
- use crate::profile::Profile;
12
- use crate::profile_serializer::ProfileSerializer;
13
- use crate::sample::Sample;
14
- use crate::scheduler::Scheduler;
15
- use crate::serialization::serializer::ProfileSerializer2;
16
- use crate::session::configuration::{self, Configuration};
17
- use crate::util::*;
18
-
19
- #[derive(Clone, Debug)]
20
- pub struct TimerThreadScheduler {
21
- configuration: Arc<Configuration>,
22
- profile: Arc<RwLock<Profile>>,
23
- stop_requested: Arc<AtomicBool>,
24
- }
25
-
26
- #[derive(Debug)]
27
- struct PostponedJobArgs {
28
- configuration: Arc<Configuration>,
29
- profile: Arc<RwLock<Profile>>,
30
- }
31
-
32
- impl Scheduler for TimerThreadScheduler {
33
- fn start(&self) -> VALUE {
34
- // Register the Postponed Job which does the actual work of collecting samples
35
- let postponed_job_args: Box<PostponedJobArgs> = Box::new(PostponedJobArgs {
36
- configuration: Arc::clone(&self.configuration),
37
- profile: Arc::clone(&self.profile),
38
- });
39
- let postponed_job_handle: rb_postponed_job_handle_t = unsafe {
40
- rb_postponed_job_preregister(
41
- 0,
42
- Some(Self::postponed_job),
43
- Box::into_raw(postponed_job_args) as *mut c_void, // FIXME: leak
44
- )
45
- };
46
-
47
- // Start a timer thread that periodically triggers postponed jobs based on configuration
48
- let configuration = Arc::clone(&self.configuration);
49
- let stop_requested = Arc::clone(&self.stop_requested);
50
- thread::spawn(move || {
51
- Self::thread_main_loop(configuration, stop_requested, postponed_job_handle)
52
- });
53
-
54
- Qtrue.into()
55
- }
56
-
57
- fn stop(&self) -> VALUE {
58
- // Stop the collector thread
59
- self.stop_requested.store(true, Ordering::Relaxed);
60
-
61
- // Finalize
62
- match self.profile.try_write() {
63
- Ok(mut profile) => {
64
- profile.flush_temporary_sample_buffer();
65
- profile.end_instant = Some(std::time::Instant::now());
66
- }
67
- Err(_) => {
68
- println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
69
- return Qfalse.into();
70
- }
71
- }
72
-
73
- let profile = self.profile.try_read().unwrap();
74
- log::debug!("Number of samples: {}", profile.samples.len());
75
-
76
- if self.configuration.use_experimental_serializer {
77
- let mut ser = ProfileSerializer2::new();
78
- ser.serialize(&profile);
79
- ser.to_ruby_hash()
80
- } else {
81
- let serialized = ProfileSerializer::serialize(&profile);
82
- let string = CString::new(serialized).unwrap();
83
- unsafe { rb_str_new_cstr(string.as_ptr()) }
84
- }
85
- }
86
-
87
- fn on_new_thread(&self, _thread: VALUE) {
88
- todo!();
89
- }
90
-
91
- fn dmark(&self) {
92
- match self.profile.read() {
93
- Ok(profile) => unsafe {
94
- profile.dmark();
95
- },
96
- Err(_) => {
97
- panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
98
- }
99
- }
100
- }
101
-
102
- fn dfree(&self) {
103
- // No-op
104
- }
105
-
106
- fn dsize(&self) -> size_t {
107
- // FIXME: Report something better
108
- std::mem::size_of::<TimerThreadScheduler>() as size_t
109
- }
110
- }
111
-
112
- impl TimerThreadScheduler {
113
- pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
114
- Self {
115
- configuration: Arc::new(configuration.clone()),
116
- profile,
117
- stop_requested: Arc::new(AtomicBool::new(false)),
118
- }
119
-
120
- // cstr!("TimerThreadScheduler only supports :wall mode."),
121
- }
122
-
123
- fn thread_main_loop(
124
- configuration: Arc<Configuration>,
125
- stop_requested: Arc<AtomicBool>,
126
- postponed_job_handle: rb_postponed_job_handle_t,
127
- ) {
128
- loop {
129
- if stop_requested.fetch_and(true, Ordering::Relaxed) {
130
- break;
131
- }
132
- unsafe {
133
- log::trace!("Triggering postponed job");
134
- rb_postponed_job_trigger(postponed_job_handle);
135
- }
136
-
137
- thread::sleep(configuration.interval);
138
- }
139
- }
140
-
141
- unsafe extern "C" fn postponed_job(ptr: *mut c_void) {
142
- unsafe {
143
- rb_gc_disable();
144
- }
145
- let args = unsafe { ManuallyDrop::new(Box::from_raw(ptr as *mut PostponedJobArgs)) };
146
-
147
- let mut profile = match args.profile.try_write() {
148
- Ok(profile) => profile,
149
- Err(_) => {
150
- // FIXME: Do we want to properly collect GC samples? I don't know yet.
151
- log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
152
- return;
153
- }
154
- };
155
-
156
- // Collect stack information from specified Ruby Threads
157
- match &args.configuration.target_ruby_threads {
158
- configuration::Threads::All => todo!(),
159
- configuration::Threads::Targeted(threads) => {
160
- for ruby_thread in threads.iter() {
161
- // Check if the thread is still alive
162
- if unsafe { rb_funcall(*ruby_thread, rb_intern(cstr!("status")), 0) }
163
- == Qfalse as u64
164
- {
165
- continue;
166
- }
167
-
168
- let sample = Sample::capture(*ruby_thread, &profile.backtrace_state);
169
- if profile.temporary_sample_buffer.push(sample).is_err() {
170
- log::debug!("Temporary sample buffer full. Dropping sample.");
171
- }
172
- }
173
- }
174
- }
175
- unsafe {
176
- rb_gc_enable();
177
- }
178
- }
179
- }
data/ext/pf2/src/util.rs DELETED
@@ -1,31 +0,0 @@
1
- use core::mem::transmute;
2
- use rb_sys::*;
3
- use std::{ffi::c_void, u128};
4
-
5
- // Convert str literal to C string literal
6
- macro_rules! cstr {
7
- ($s:expr) => {
8
- concat!($s, "\0").as_ptr() as *const std::ffi::c_char
9
- };
10
- }
11
- pub(crate) use cstr;
12
-
13
- pub type RubyCFunc = unsafe extern "C" fn() -> VALUE;
14
-
15
- // TODO: rewrite as macro
16
- pub fn to_ruby_cfunc_with_no_args<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
17
- unsafe { transmute::<unsafe extern "C" fn(T) -> VALUE, RubyCFunc>(f) }
18
- }
19
- pub fn to_ruby_cfunc_with_args<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
20
- unsafe { transmute::<unsafe extern "C" fn(T, U, V) -> VALUE, RubyCFunc>(f) }
21
- }
22
-
23
- #[allow(non_snake_case)]
24
- pub fn RTEST(v: VALUE) -> bool {
25
- v != Qfalse as VALUE && v != Qnil as VALUE
26
- }
27
-
28
- extern "C" {
29
- pub fn extract_si_value_sival_ptr(info: *mut libc::siginfo_t) -> *mut c_void;
30
- pub fn rb_ull2num(n: u128) -> VALUE;
31
- }