Skip to content

Commit 5666e5b

Browse files
foobar
1 parent 0994638 commit 5666e5b

File tree

3 files changed

+123
-31
lines changed

3 files changed

+123
-31
lines changed

profiling/src/profiling/interrupts.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,4 @@ impl InterruptManager {
5353
(*interrupt.interrupt_count_ptr).store(0, Ordering::SeqCst);
5454
}
5555
}
56-
57-
#[inline]
58-
pub(super) fn has_interrupts(&self) -> bool {
59-
!self.vm_interrupts.lock().unwrap().is_empty()
60-
}
61-
62-
pub(super) fn trigger_interrupts(&self) {
63-
let vm_interrupts = self.vm_interrupts.lock().unwrap();
64-
vm_interrupts.iter().for_each(|obj| unsafe {
65-
(*obj.interrupt_count_ptr).fetch_add(1, Ordering::SeqCst);
66-
(*obj.engine_ptr).store(true, Ordering::SeqCst);
67-
});
68-
}
6956
}

profiling/src/profiling/mod.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod sample_type_filter;
33
pub mod stack_walking;
44
mod thread_utils;
55
mod uploader;
6+
mod signals;
67

78
pub use interrupts::*;
89
pub use sample_type_filter::*;
@@ -232,7 +233,6 @@ pub struct Profiler {
232233

233234
struct TimeCollector {
234235
fork_barrier: Arc<Barrier>,
235-
interrupt_manager: Arc<InterruptManager>,
236236
message_receiver: Receiver<ProfilerMessage>,
237237
upload_sender: Sender<UploadMessage>,
238238
upload_period: Duration,
@@ -560,9 +560,7 @@ impl TimeCollector {
560560
UPLOAD_PERIOD.as_secs(),
561561
WALL_TIME_PERIOD.as_millis());
562562

563-
let wall_timer = crossbeam_channel::tick(WALL_TIME_PERIOD);
564563
let upload_tick = crossbeam_channel::tick(self.upload_period);
565-
let never = crossbeam_channel::never();
566564
let mut running = true;
567565

568566
while running {
@@ -572,11 +570,6 @@ impl TimeCollector {
572570
// iteration instead, keeping the code structure of the recvs the
573571
// same. Since the never channel will never be ready, this
574572
// effectively makes that branch optional for that loop iteration.
575-
let timer = if self.interrupt_manager.has_interrupts() {
576-
&wall_timer
577-
} else {
578-
&never
579-
};
580573

581574
crossbeam_channel::select! {
582575

@@ -615,15 +608,6 @@ impl TimeCollector {
615608
}
616609
},
617610

618-
recv(timer) -> message => match message {
619-
Ok(_) => self.interrupt_manager.trigger_interrupts(),
620-
621-
Err(err) => {
622-
warn!("{err}");
623-
break;
624-
},
625-
},
626-
627611
recv(upload_tick) -> message => {
628612
if message.is_ok() {
629613
last_wall_export = self.handle_timeout(&mut profiles, &last_wall_export);
@@ -669,13 +653,15 @@ impl Profiler {
669653
}
670654

671655
pub fn new(system_settings: &mut SystemSettings) -> Self {
656+
// Start the signal-based profiling mechanism (Phase 1)
657+
signals::start_profiling_mechanism();
658+
672659
let fork_barrier = Arc::new(Barrier::new(3));
673660
let interrupt_manager = Arc::new(InterruptManager::new());
674661
let (message_sender, message_receiver) = crossbeam_channel::bounded(100);
675662
let (upload_sender, upload_receiver) = crossbeam_channel::bounded(UPLOAD_CHANNEL_CAPACITY);
676663
let time_collector = TimeCollector {
677664
fork_barrier: fork_barrier.clone(),
678-
interrupt_manager: interrupt_manager.clone(),
679665
message_receiver,
680666
upload_sender: upload_sender.clone(),
681667
upload_period: UPLOAD_PERIOD,

profiling/src/profiling/signals.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use crate::bindings::ddog_php_prof_get_current_execute_data;
2+
use crate::Profiler;
3+
use libc::{c_int, c_void, sigaction, siginfo_t, SA_RESTART, SA_SIGINFO};
4+
use std::mem;
5+
use std::ptr;
6+
use std::sync::atomic::{AtomicBool, Ordering};
7+
8+
static SIGNAL_HANDLER_ACTIVE: AtomicBool = AtomicBool::new(false);
9+
10+
extern "C" fn profiling_signal_handler(_signum: c_int, _info: *mut siginfo_t, _context: *mut c_void) {
11+
// Avoid re-entrancy if possible, though strict re-entrancy isn't guaranteed with this logic alone
12+
// in a signal handler. However, for profiling, we usually just skip if busy.
13+
// For now, let's just try to collect.
14+
15+
// Safety: Accessing global state in a signal handler is risky.
16+
// Profiler::get() reads a static OnceCell.
17+
if let Some(profiler) = Profiler::get() {
18+
let execute_data = unsafe { ddog_php_prof_get_current_execute_data() };
19+
if !execute_data.is_null() {
20+
profiler.collect_time(execute_data, 1);
21+
}
22+
}
23+
}
24+
25+
pub fn start_profiling_mechanism() {
26+
if SIGNAL_HANDLER_ACTIVE.swap(true, Ordering::SeqCst) {
27+
return; // Already started
28+
}
29+
30+
#[cfg(target_os = "linux")]
31+
start_linux_timer();
32+
33+
#[cfg(target_os = "macos")]
34+
start_macos_sidecar();
35+
}
36+
37+
#[cfg(target_os = "linux")]
38+
fn start_linux_timer() {
39+
use libc::{
40+
timer_create, timer_settime, sigevent, itimerspec, timespec,
41+
CLOCK_MONOTONIC, SIGEV_THREAD, SIGRTMIN, syscall, SYS_gettid
42+
};
43+
44+
unsafe {
45+
// 1. Pick a signal: SIGRTMIN + 1
46+
// SIGRTMIN is a macro or function in libc usually.
47+
let signo = SIGRTMIN() + 1;
48+
49+
// 2. Register Signal Handler
50+
let mut sa: sigaction = mem::zeroed();
51+
sa.sa_sigaction = profiling_signal_handler as usize;
52+
sa.sa_flags = SA_SIGINFO | SA_RESTART;
53+
libc::sigemptyset(&mut sa.sa_mask);
54+
libc::sigaction(signo, &sa, ptr::null_mut());
55+
56+
// 3. Create Timer
57+
let mut sev: sigevent = mem::zeroed();
58+
sev.sigev_notify = SIGEV_THREAD;
59+
sev.sigev_signo = signo;
60+
sev.sigev_value.sival_ptr = ptr::null_mut();
61+
// Target the current thread (the PHP main thread)
62+
sev.sigev_notify_thread_id = syscall(SYS_gettid) as i32;
63+
64+
let mut timer_id: libc::timer_t = mem::zeroed();
65+
if timer_create(CLOCK_MONOTONIC, &mut sev, &mut timer_id) == 0 {
66+
// 4. Arm Timer (10ms)
67+
let interval = timespec {
68+
tv_sec: 0,
69+
tv_nsec: 10 * 1_000_000, // 10ms
70+
};
71+
let its = itimerspec {
72+
it_interval: interval,
73+
it_value: interval,
74+
};
75+
timer_settime(timer_id, 0, &its, ptr::null_mut());
76+
77+
// We might want to store timer_id somewhere to delete it later,
78+
// but for this example we leak it or let it die with the process.
79+
}
80+
}
81+
}
82+
83+
#[cfg(target_os = "macos")]
84+
fn start_macos_sidecar() {
85+
use libc::{SIGURG, pthread_self, pthread_t};
86+
use std::thread;
87+
use std::time::Duration;
88+
89+
unsafe {
90+
// 1. Register Signal Handler
91+
let signo = SIGURG;
92+
let mut sa: sigaction = mem::zeroed();
93+
sa.sa_sigaction = profiling_signal_handler as usize;
94+
// SA_RESTART is important so we don't interrupt blocking syscalls like read() with EINTR too often
95+
// unless we want to? For profiling, we generally want to see what's happening.
96+
sa.sa_flags = SA_SIGINFO | SA_RESTART;
97+
libc::sigemptyset(&mut sa.sa_mask);
98+
libc::sigaction(signo, &sa, ptr::null_mut());
99+
100+
// 2. Capture Main Thread ID
101+
let main_thread: pthread_t = pthread_self();
102+
103+
// 3. Spawn Sidecar Thread
104+
thread::Builder::new()
105+
.name("ddprof_sidecar".to_string())
106+
.spawn(move || {
107+
loop {
108+
thread::sleep(Duration::from_millis(10));
109+
// Send signal to main thread
110+
let ret = libc::pthread_kill(main_thread, signo);
111+
if ret != 0 {
112+
// Main thread might have exited?
113+
break;
114+
}
115+
}
116+
})
117+
.expect("Failed to spawn sidecar thread");
118+
}
119+
}

0 commit comments

Comments
 (0)