sandbox_rs/monitoring/
ebpf.rs1use crate::errors::Result;
9use nix::unistd::Pid;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct SyscallEvent {
15 pub syscall_id: u64,
17 pub syscall_name: String,
19 pub duration_us: u64,
21 pub timestamp: u64,
23 pub is_slow: bool,
25}
26
27impl SyscallEvent {
28 pub fn is_slow_syscall(&self) -> bool {
30 self.duration_us > 10_000 }
32
33 pub fn duration_ms(&self) -> f64 {
35 self.duration_us as f64 / 1000.0
36 }
37}
38
39#[derive(Debug, Clone, Default)]
41pub struct SyscallStats {
42 pub total_syscalls: u64,
44 pub slow_syscalls: u64,
46 pub total_time_us: u64,
48 pub syscalls_by_name: HashMap<String, (u64, u64)>, pub slowest_syscalls: Vec<SyscallEvent>,
52}
53
54pub struct EBpfMonitor {
56 pid: Pid,
57 events: Vec<SyscallEvent>,
58 stats: SyscallStats,
59}
60
61impl EBpfMonitor {
62 pub fn new(pid: Pid) -> Self {
64 EBpfMonitor {
65 pid,
66 events: Vec::new(),
67 stats: SyscallStats::default(),
68 }
69 }
70
71 pub fn collect_stats(&mut self) -> Result<SyscallStats> {
79 self._compute_statistics();
82 Ok(self.stats.clone())
83 }
84
85 pub fn add_event(&mut self, event: SyscallEvent) {
87 self.events.push(event);
88 self._compute_statistics();
89 }
90
91 pub fn clear(&mut self) {
93 self.events.clear();
94 self.stats = SyscallStats::default();
95 }
96
97 pub fn pid(&self) -> Pid {
99 self.pid
100 }
101
102 pub fn slow_syscall_count(&self) -> u64 {
104 self.stats.slow_syscalls
105 }
106
107 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 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 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 slowest.push(event.clone());
138 }
139
140 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, 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, 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, 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 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 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}