sandbox_rs/monitoring/
ebpf.rs

1//! eBPF-based syscall monitoring
2//!
3//! Provides event-driven syscall tracing using eBPF programs.
4//! Monitors syscall frequency, duration, and detects slow operations (>10ms).
5//!
6//! Note: Full eBPF functionality requires kernel 5.0+ and BPF_RING_BUFFER support.
7
8use crate::errors::Result;
9use nix::unistd::Pid;
10use std::collections::HashMap;
11
12/// Syscall event information
13#[derive(Debug, Clone)]
14pub struct SyscallEvent {
15    /// Syscall number
16    pub syscall_id: u64,
17    /// Syscall name (e.g., "read", "write")
18    pub syscall_name: String,
19    /// Duration in microseconds
20    pub duration_us: u64,
21    /// Timestamp when syscall occurred
22    pub timestamp: u64,
23    /// Whether this was a slow syscall (>10ms)
24    pub is_slow: bool,
25}
26
27impl SyscallEvent {
28    /// Check if this syscall is considered slow (>10ms)
29    pub fn is_slow_syscall(&self) -> bool {
30        self.duration_us > 10_000 // 10ms in microseconds
31    }
32
33    /// Get duration in milliseconds
34    pub fn duration_ms(&self) -> f64 {
35        self.duration_us as f64 / 1000.0
36    }
37}
38
39/// Aggregated syscall statistics
40#[derive(Debug, Clone, Default)]
41pub struct SyscallStats {
42    /// Total number of syscalls
43    pub total_syscalls: u64,
44    /// Number of slow syscalls (>10ms)
45    pub slow_syscalls: u64,
46    /// Total time spent in syscalls (microseconds)
47    pub total_time_us: u64,
48    /// Syscalls by name with their count and total duration
49    pub syscalls_by_name: HashMap<String, (u64, u64)>, // (count, total_time_us)
50    /// Top N slowest syscalls
51    pub slowest_syscalls: Vec<SyscallEvent>,
52}
53
54/// eBPF-based syscall monitor
55pub struct EBpfMonitor {
56    pid: Pid,
57    events: Vec<SyscallEvent>,
58    stats: SyscallStats,
59}
60
61impl EBpfMonitor {
62    /// Create new eBPF monitor for process
63    pub fn new(pid: Pid) -> Self {
64        EBpfMonitor {
65            pid,
66            events: Vec::new(),
67            stats: SyscallStats::default(),
68        }
69    }
70
71    /// Collect syscall statistics
72    pub fn collect_stats(&mut self) -> Result<SyscallStats> {
73        // This is a placeholder implementation
74        self._compute_statistics();
75        Ok(self.stats.clone())
76    }
77
78    /// Add raw syscall event (for testing/manual injection)
79    pub fn add_event(&mut self, event: SyscallEvent) {
80        self.events.push(event);
81        self._compute_statistics();
82    }
83
84    /// Clear collected events
85    pub fn clear(&mut self) {
86        self.events.clear();
87        self.stats = SyscallStats::default();
88    }
89
90    /// Get process ID being monitored
91    pub fn pid(&self) -> Pid {
92        self.pid
93    }
94
95    /// Get total slow syscalls
96    pub fn slow_syscall_count(&self) -> u64 {
97        self.stats.slow_syscalls
98    }
99
100    /// Get top N slowest syscalls
101    pub fn slowest_syscalls(&self, n: usize) -> Vec<SyscallEvent> {
102        self.stats
103            .slowest_syscalls
104            .iter()
105            .take(n)
106            .cloned()
107            .collect()
108    }
109
110    /// Recompute statistics from events
111    fn _compute_statistics(&mut self) {
112        let mut stats = SyscallStats::default();
113        let mut by_name: HashMap<String, (u64, u64)> = HashMap::new();
114        let mut slowest: Vec<SyscallEvent> = Vec::new();
115
116        for event in &self.events {
117            stats.total_syscalls += 1;
118            stats.total_time_us += event.duration_us;
119
120            if event.is_slow {
121                stats.slow_syscalls += 1;
122            }
123
124            // Aggregate by syscall name
125            let entry = by_name.entry(event.syscall_name.clone()).or_insert((0, 0));
126            entry.0 += 1;
127            entry.1 += event.duration_us;
128
129            // Track slowest syscalls
130            slowest.push(event.clone());
131        }
132
133        // Sort slowest and keep top 10
134        slowest.sort_by(|a, b| b.duration_us.cmp(&a.duration_us));
135        slowest.truncate(10);
136
137        stats.syscalls_by_name = by_name;
138        stats.slowest_syscalls = slowest;
139
140        self.stats = stats;
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_syscall_event_is_slow() {
150        let event_slow = SyscallEvent {
151            syscall_id: 1,
152            syscall_name: "read".to_string(),
153            duration_us: 15_000, // 15ms
154            timestamp: 0,
155            is_slow: true,
156        };
157        assert!(event_slow.is_slow_syscall());
158
159        let event_fast = SyscallEvent {
160            syscall_id: 1,
161            syscall_name: "read".to_string(),
162            duration_us: 5_000, // 5ms
163            timestamp: 0,
164            is_slow: false,
165        };
166        assert!(!event_fast.is_slow_syscall());
167    }
168
169    #[test]
170    fn test_syscall_event_duration_ms() {
171        let event = SyscallEvent {
172            syscall_id: 1,
173            syscall_name: "write".to_string(),
174            duration_us: 10_000, // 10ms
175            timestamp: 0,
176            is_slow: false,
177        };
178        assert_eq!(event.duration_ms(), 10.0);
179    }
180
181    #[test]
182    fn test_ebpf_monitor_new() {
183        let pid = Pid::from_raw(std::process::id() as i32);
184        let monitor = EBpfMonitor::new(pid);
185        assert_eq!(monitor.pid(), pid);
186        assert_eq!(monitor.slow_syscall_count(), 0);
187    }
188
189    #[test]
190    fn test_ebpf_monitor_add_event() {
191        let pid = Pid::from_raw(std::process::id() as i32);
192        let mut monitor = EBpfMonitor::new(pid);
193
194        let event = SyscallEvent {
195            syscall_id: 1,
196            syscall_name: "read".to_string(),
197            duration_us: 5_000,
198            timestamp: 0,
199            is_slow: false,
200        };
201
202        monitor.add_event(event);
203        assert_eq!(monitor.stats.total_syscalls, 1);
204        assert_eq!(monitor.stats.slow_syscalls, 0);
205    }
206
207    #[test]
208    fn test_ebpf_monitor_slow_syscalls() {
209        let pid = Pid::from_raw(std::process::id() as i32);
210        let mut monitor = EBpfMonitor::new(pid);
211
212        // Add fast syscall
213        monitor.add_event(SyscallEvent {
214            syscall_id: 1,
215            syscall_name: "read".to_string(),
216            duration_us: 5_000,
217            timestamp: 0,
218            is_slow: false,
219        });
220
221        // Add slow syscall
222        monitor.add_event(SyscallEvent {
223            syscall_id: 2,
224            syscall_name: "write".to_string(),
225            duration_us: 15_000,
226            timestamp: 1,
227            is_slow: true,
228        });
229
230        assert_eq!(monitor.stats.total_syscalls, 2);
231        assert_eq!(monitor.stats.slow_syscalls, 1);
232    }
233
234    #[test]
235    fn test_ebpf_monitor_slowest_syscalls() {
236        let pid = Pid::from_raw(std::process::id() as i32);
237        let mut monitor = EBpfMonitor::new(pid);
238
239        for i in 0..5 {
240            monitor.add_event(SyscallEvent {
241                syscall_id: i,
242                syscall_name: format!("syscall_{}", i),
243                duration_us: (i + 1) * 1000,
244                timestamp: i,
245                is_slow: (i + 1) * 1000 > 10_000,
246            });
247        }
248
249        let slowest = monitor.slowest_syscalls(3);
250        assert_eq!(slowest.len(), 3);
251    }
252
253    #[test]
254    fn test_ebpf_monitor_clear() {
255        let pid = Pid::from_raw(std::process::id() as i32);
256        let mut monitor = EBpfMonitor::new(pid);
257
258        monitor.add_event(SyscallEvent {
259            syscall_id: 1,
260            syscall_name: "read".to_string(),
261            duration_us: 5_000,
262            timestamp: 0,
263            is_slow: false,
264        });
265
266        assert_eq!(monitor.stats.total_syscalls, 1);
267
268        monitor.clear();
269        assert_eq!(monitor.stats.total_syscalls, 0);
270        assert_eq!(monitor.stats.slow_syscalls, 0);
271    }
272}