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    ///
73    /// In a production implementation, this would:
74    /// 1. Attach eBPF programs to kernel tracepoints
75    /// 2. Use ring buffers to collect syscall events
76    /// 3. Aggregate statistics from collected events
77    /// 4. Analyze slow syscalls
78    pub fn collect_stats(&mut self) -> Result<SyscallStats> {
79        // This is a placeholder implementation
80        // Real implementation would use eBPF via libbpf or similar
81        self._compute_statistics();
82        Ok(self.stats.clone())
83    }
84
85    /// Add raw syscall event (for testing/manual injection)
86    pub fn add_event(&mut self, event: SyscallEvent) {
87        self.events.push(event);
88        self._compute_statistics();
89    }
90
91    /// Clear collected events
92    pub fn clear(&mut self) {
93        self.events.clear();
94        self.stats = SyscallStats::default();
95    }
96
97    /// Get process ID being monitored
98    pub fn pid(&self) -> Pid {
99        self.pid
100    }
101
102    /// Get total slow syscalls
103    pub fn slow_syscall_count(&self) -> u64 {
104        self.stats.slow_syscalls
105    }
106
107    /// Get top N slowest syscalls
108    pub fn slowest_syscalls(&self, n: usize) -> Vec<SyscallEvent> {
109        self.stats
110            .slowest_syscalls
111            .iter()
112            .take(n)
113            .cloned()
114            .collect()
115    }
116
117    /// Recompute statistics from events
118    fn _compute_statistics(&mut self) {
119        let mut stats = SyscallStats::default();
120        let mut by_name: HashMap<String, (u64, u64)> = HashMap::new();
121        let mut slowest: Vec<SyscallEvent> = Vec::new();
122
123        for event in &self.events {
124            stats.total_syscalls += 1;
125            stats.total_time_us += event.duration_us;
126
127            if event.is_slow {
128                stats.slow_syscalls += 1;
129            }
130
131            // Aggregate by syscall name
132            let entry = by_name.entry(event.syscall_name.clone()).or_insert((0, 0));
133            entry.0 += 1;
134            entry.1 += event.duration_us;
135
136            // Track slowest syscalls
137            slowest.push(event.clone());
138        }
139
140        // Sort slowest and keep top 10
141        slowest.sort_by(|a, b| b.duration_us.cmp(&a.duration_us));
142        slowest.truncate(10);
143
144        stats.syscalls_by_name = by_name;
145        stats.slowest_syscalls = slowest;
146
147        self.stats = stats;
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_syscall_event_is_slow() {
157        let event_slow = SyscallEvent {
158            syscall_id: 1,
159            syscall_name: "read".to_string(),
160            duration_us: 15_000, // 15ms
161            timestamp: 0,
162            is_slow: true,
163        };
164        assert!(event_slow.is_slow_syscall());
165
166        let event_fast = SyscallEvent {
167            syscall_id: 1,
168            syscall_name: "read".to_string(),
169            duration_us: 5_000, // 5ms
170            timestamp: 0,
171            is_slow: false,
172        };
173        assert!(!event_fast.is_slow_syscall());
174    }
175
176    #[test]
177    fn test_syscall_event_duration_ms() {
178        let event = SyscallEvent {
179            syscall_id: 1,
180            syscall_name: "write".to_string(),
181            duration_us: 10_000, // 10ms
182            timestamp: 0,
183            is_slow: false,
184        };
185        assert_eq!(event.duration_ms(), 10.0);
186    }
187
188    #[test]
189    fn test_ebpf_monitor_new() {
190        let pid = Pid::from_raw(std::process::id() as i32);
191        let monitor = EBpfMonitor::new(pid);
192        assert_eq!(monitor.pid(), pid);
193        assert_eq!(monitor.slow_syscall_count(), 0);
194    }
195
196    #[test]
197    fn test_ebpf_monitor_add_event() {
198        let pid = Pid::from_raw(std::process::id() as i32);
199        let mut monitor = EBpfMonitor::new(pid);
200
201        let event = SyscallEvent {
202            syscall_id: 1,
203            syscall_name: "read".to_string(),
204            duration_us: 5_000,
205            timestamp: 0,
206            is_slow: false,
207        };
208
209        monitor.add_event(event);
210        assert_eq!(monitor.stats.total_syscalls, 1);
211        assert_eq!(monitor.stats.slow_syscalls, 0);
212    }
213
214    #[test]
215    fn test_ebpf_monitor_slow_syscalls() {
216        let pid = Pid::from_raw(std::process::id() as i32);
217        let mut monitor = EBpfMonitor::new(pid);
218
219        // Add fast syscall
220        monitor.add_event(SyscallEvent {
221            syscall_id: 1,
222            syscall_name: "read".to_string(),
223            duration_us: 5_000,
224            timestamp: 0,
225            is_slow: false,
226        });
227
228        // Add slow syscall
229        monitor.add_event(SyscallEvent {
230            syscall_id: 2,
231            syscall_name: "write".to_string(),
232            duration_us: 15_000,
233            timestamp: 1,
234            is_slow: true,
235        });
236
237        assert_eq!(monitor.stats.total_syscalls, 2);
238        assert_eq!(monitor.stats.slow_syscalls, 1);
239    }
240
241    #[test]
242    fn test_ebpf_monitor_slowest_syscalls() {
243        let pid = Pid::from_raw(std::process::id() as i32);
244        let mut monitor = EBpfMonitor::new(pid);
245
246        for i in 0..5 {
247            monitor.add_event(SyscallEvent {
248                syscall_id: i,
249                syscall_name: format!("syscall_{}", i),
250                duration_us: (i + 1) * 1000,
251                timestamp: i,
252                is_slow: (i + 1) * 1000 > 10_000,
253            });
254        }
255
256        let slowest = monitor.slowest_syscalls(3);
257        assert_eq!(slowest.len(), 3);
258    }
259
260    #[test]
261    fn test_ebpf_monitor_clear() {
262        let pid = Pid::from_raw(std::process::id() as i32);
263        let mut monitor = EBpfMonitor::new(pid);
264
265        monitor.add_event(SyscallEvent {
266            syscall_id: 1,
267            syscall_name: "read".to_string(),
268            duration_us: 5_000,
269            timestamp: 0,
270            is_slow: false,
271        });
272
273        assert_eq!(monitor.stats.total_syscalls, 1);
274
275        monitor.clear();
276        assert_eq!(monitor.stats.total_syscalls, 0);
277        assert_eq!(monitor.stats.slow_syscalls, 0);
278    }
279}