use nix::unistd::Pid;
use sandbox_core::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct SyscallEvent {
pub syscall_id: u64,
pub syscall_name: String,
pub duration_us: u64,
pub timestamp: u64,
pub is_slow: bool,
}
impl SyscallEvent {
pub fn is_slow_syscall(&self) -> bool {
self.duration_us > 10_000
}
pub fn duration_ms(&self) -> f64 {
self.duration_us as f64 / 1000.0
}
}
#[derive(Debug, Clone, Default)]
pub struct SyscallStats {
pub total_syscalls: u64,
pub slow_syscalls: u64,
pub total_time_us: u64,
pub syscalls_by_name: HashMap<String, (u64, u64)>,
pub slowest_syscalls: Vec<SyscallEvent>,
}
pub struct EBpfMonitor {
pid: Pid,
events: Vec<SyscallEvent>,
stats: SyscallStats,
}
impl EBpfMonitor {
pub fn new(pid: Pid) -> Self {
EBpfMonitor {
pid,
events: Vec::new(),
stats: SyscallStats::default(),
}
}
pub fn collect_stats(&mut self) -> Result<SyscallStats> {
self._compute_statistics();
Ok(self.stats.clone())
}
pub fn add_event(&mut self, event: SyscallEvent) {
self.events.push(event);
self._compute_statistics();
}
pub fn clear(&mut self) {
self.events.clear();
self.stats = SyscallStats::default();
}
pub fn pid(&self) -> Pid {
self.pid
}
pub fn slow_syscall_count(&self) -> u64 {
self.stats.slow_syscalls
}
pub fn slowest_syscalls(&self, n: usize) -> Vec<SyscallEvent> {
self.stats
.slowest_syscalls
.iter()
.take(n)
.cloned()
.collect()
}
fn _compute_statistics(&mut self) {
let mut stats = SyscallStats::default();
let mut by_name: HashMap<String, (u64, u64)> = HashMap::new();
let mut slowest: Vec<SyscallEvent> = Vec::new();
for event in &self.events {
stats.total_syscalls += 1;
stats.total_time_us += event.duration_us;
if event.is_slow {
stats.slow_syscalls += 1;
}
let entry = by_name.entry(event.syscall_name.clone()).or_insert((0, 0));
entry.0 += 1;
entry.1 += event.duration_us;
slowest.push(event.clone());
}
slowest.sort_by(|a, b| b.duration_us.cmp(&a.duration_us));
slowest.truncate(10);
stats.syscalls_by_name = by_name;
stats.slowest_syscalls = slowest;
self.stats = stats;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_syscall_event_is_slow() {
let event_slow = SyscallEvent {
syscall_id: 1,
syscall_name: "read".to_string(),
duration_us: 15_000,
timestamp: 0,
is_slow: true,
};
assert!(event_slow.is_slow_syscall());
let event_fast = SyscallEvent {
syscall_id: 1,
syscall_name: "read".to_string(),
duration_us: 5_000,
timestamp: 0,
is_slow: false,
};
assert!(!event_fast.is_slow_syscall());
}
#[test]
fn test_ebpf_monitor_new() {
let pid = Pid::from_raw(std::process::id() as i32);
let monitor = EBpfMonitor::new(pid);
assert_eq!(monitor.pid(), pid);
assert_eq!(monitor.slow_syscall_count(), 0);
}
#[test]
fn test_ebpf_monitor_add_event() {
let pid = Pid::from_raw(std::process::id() as i32);
let mut monitor = EBpfMonitor::new(pid);
monitor.add_event(SyscallEvent {
syscall_id: 1,
syscall_name: "read".to_string(),
duration_us: 5_000,
timestamp: 0,
is_slow: false,
});
assert_eq!(monitor.stats.total_syscalls, 1);
assert_eq!(monitor.stats.slow_syscalls, 0);
}
#[test]
fn test_ebpf_monitor_clear() {
let pid = Pid::from_raw(std::process::id() as i32);
let mut monitor = EBpfMonitor::new(pid);
monitor.add_event(SyscallEvent {
syscall_id: 1,
syscall_name: "read".to_string(),
duration_us: 5_000,
timestamp: 0,
is_slow: false,
});
assert_eq!(monitor.stats.total_syscalls, 1);
monitor.clear();
assert_eq!(monitor.stats.total_syscalls, 0);
}
}