sandbox_rs/monitoring/
monitor.rs1use std::fs;
7use std::path::Path;
8use std::time::{Duration, Instant};
9
10use nix::sys::signal::{Signal, kill};
11use nix::unistd::Pid;
12
13use crate::errors::{Result, SandboxError};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ProcessState {
18 Running,
20 Sleeping,
22 Zombie,
24 Unknown,
26}
27
28impl ProcessState {
29 pub fn from_char(c: char) -> Self {
31 match c {
32 'R' => ProcessState::Running,
33 'S' => ProcessState::Sleeping,
34 'Z' => ProcessState::Zombie,
35 _ => ProcessState::Unknown,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct ProcessStats {
43 pub pid: i32,
45 pub vsize: u64,
47 pub rss: u64,
49 pub memory_usage_mb: u64,
51 pub cpu_time_ms: u64,
53 pub num_threads: u32,
55 pub state: ProcessState,
57 pub timestamp: Instant,
59}
60
61impl ProcessStats {
62 fn from_proc(pid: i32, timestamp: Instant) -> Result<Self> {
64 let stat_path = format!("/proc/{}/stat", pid);
65 let status_path = format!("/proc/{}/status", pid);
66
67 let stat_content = fs::read_to_string(&stat_path).map_err(|e| {
69 SandboxError::ProcessMonitoring(format!("Failed to read {}: {}", stat_path, e))
70 })?;
71
72 let parts: Vec<&str> = stat_content.split_whitespace().collect();
74 if parts.len() < 20 {
75 return Err(SandboxError::ProcessMonitoring(
76 "Invalid /proc/stat format".to_string(),
77 ));
78 }
79
80 let state = ProcessState::from_char(parts[2].chars().next().unwrap_or('?'));
81 let utime: u64 = parts[13]
82 .parse()
83 .map_err(|_| SandboxError::ProcessMonitoring("Invalid utime".to_string()))?;
84 let stime: u64 = parts[14]
85 .parse()
86 .map_err(|_| SandboxError::ProcessMonitoring("Invalid stime".to_string()))?;
87 let num_threads: u32 = parts[19]
88 .parse()
89 .map_err(|_| SandboxError::ProcessMonitoring("Invalid num_threads".to_string()))?;
90 let vsize: u64 = parts[22]
91 .parse()
92 .map_err(|_| SandboxError::ProcessMonitoring("Invalid vsize".to_string()))?;
93 let rss: u64 = parts[23]
94 .parse()
95 .map_err(|_| SandboxError::ProcessMonitoring("Invalid rss".to_string()))?;
96
97 let _status_content = fs::read_to_string(&status_path).unwrap_or_default();
99
100 let clk_tck = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as u64;
103 let cpu_time_ms = if clk_tck > 0 {
104 ((utime + stime) * 1000) / clk_tck
105 } else {
106 0
107 };
108
109 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64;
111 let rss_bytes = rss * page_size;
112 let memory_usage_mb = rss_bytes / (1024 * 1024);
113
114 Ok(ProcessStats {
115 pid,
116 vsize,
117 rss: rss_bytes,
118 memory_usage_mb,
119 cpu_time_ms,
120 num_threads,
121 state,
122 timestamp,
123 })
124 }
125}
126
127pub struct ProcessMonitor {
129 pid: Pid,
130 creation_time: Instant,
131 peak_memory_mb: u64,
132 last_stats: Option<ProcessStats>,
133}
134
135impl ProcessMonitor {
136 pub fn new(pid: Pid) -> Result<Self> {
138 let stat_path = format!("/proc/{}/stat", pid.as_raw());
140 if !Path::new(&stat_path).exists() {
141 return Err(SandboxError::ProcessMonitoring(format!(
142 "Process {} not found",
143 pid
144 )));
145 }
146
147 Ok(ProcessMonitor {
148 pid,
149 creation_time: Instant::now(),
150 peak_memory_mb: 0,
151 last_stats: None,
152 })
153 }
154
155 pub fn collect_stats(&mut self) -> Result<ProcessStats> {
157 let now = Instant::now();
158 let stats = ProcessStats::from_proc(self.pid.as_raw(), now)?;
159
160 if stats.memory_usage_mb > self.peak_memory_mb {
162 self.peak_memory_mb = stats.memory_usage_mb;
163 }
164
165 self.last_stats = Some(stats.clone());
166 Ok(stats)
167 }
168
169 pub fn peak_memory_mb(&self) -> u64 {
171 self.peak_memory_mb
172 }
173
174 pub fn elapsed(&self) -> Duration {
176 self.creation_time.elapsed()
177 }
178
179 pub fn is_alive(&self) -> Result<bool> {
181 let stat_path = format!("/proc/{}/stat", self.pid.as_raw());
182 Ok(Path::new(&stat_path).exists())
183 }
184
185 pub fn send_sigterm(&self) -> Result<()> {
187 kill(self.pid, Signal::SIGTERM)
188 .map_err(|e| SandboxError::Syscall(format!("Failed to send SIGTERM: {}", e)))
189 }
190
191 pub fn send_sigkill(&self) -> Result<()> {
193 kill(self.pid, Signal::SIGKILL)
194 .map_err(|e| SandboxError::Syscall(format!("Failed to send SIGKILL: {}", e)))
195 }
196
197 pub fn graceful_shutdown(&self, wait_duration: Duration) -> Result<()> {
199 self.send_sigterm()?;
201
202 let start = Instant::now();
204 while start.elapsed() < wait_duration && self.is_alive()? {
205 std::thread::sleep(Duration::from_millis(10));
206 }
207
208 if self.is_alive()? {
210 self.send_sigkill()?;
211 }
212
213 Ok(())
214 }
215
216 pub fn last_stats(&self) -> Option<&ProcessStats> {
218 self.last_stats.as_ref()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_process_state_from_char() {
228 assert_eq!(ProcessState::from_char('R'), ProcessState::Running);
229 assert_eq!(ProcessState::from_char('S'), ProcessState::Sleeping);
230 assert_eq!(ProcessState::from_char('Z'), ProcessState::Zombie);
231 assert_eq!(ProcessState::from_char('X'), ProcessState::Unknown);
232 }
233
234 #[test]
235 fn test_process_stats_creation() {
236 let pid = std::process::id() as i32;
238 let timestamp = Instant::now();
239 let result = ProcessStats::from_proc(pid, timestamp);
240 assert!(result.is_ok());
241
242 if let Ok(stats) = result {
243 assert_eq!(stats.pid, pid);
244 assert!(stats.memory_usage_mb > 0);
245 }
246 }
247
248 #[test]
249 fn test_process_monitor_new() {
250 let pid = Pid::from_raw(std::process::id() as i32);
251 let result = ProcessMonitor::new(pid);
252 assert!(result.is_ok());
253 }
254
255 #[test]
256 fn test_process_monitor_is_alive() {
257 let pid = Pid::from_raw(std::process::id() as i32);
258 let monitor = ProcessMonitor::new(pid).unwrap();
259 assert!(monitor.is_alive().unwrap());
260 }
261
262 #[test]
263 fn test_process_monitor_collect_stats() {
264 let pid = Pid::from_raw(std::process::id() as i32);
265 let mut monitor = ProcessMonitor::new(pid).unwrap();
266 let stats = monitor.collect_stats().unwrap();
267
268 assert_eq!(stats.pid, pid.as_raw());
269 assert!(stats.memory_usage_mb > 0);
270 assert_eq!(monitor.peak_memory_mb(), stats.memory_usage_mb);
271 }
272
273 #[test]
274 fn test_process_monitor_peak_memory() {
275 let pid = Pid::from_raw(std::process::id() as i32);
276 let mut monitor = ProcessMonitor::new(pid).unwrap();
277
278 monitor.collect_stats().unwrap();
279 let peak1 = monitor.peak_memory_mb();
280
281 monitor.collect_stats().unwrap();
282 let peak2 = monitor.peak_memory_mb();
283
284 assert!(peak1 > 0);
285 assert!(peak2 >= peak1);
286 }
287
288 #[test]
289 fn test_process_stats_from_proc_missing_file() {
290 let invalid_pid = 9_999_999i32;
291 let timestamp = Instant::now();
292 let result = ProcessStats::from_proc(invalid_pid, timestamp);
293 assert!(result.is_err());
294 }
295
296 #[test]
297 fn test_process_stats_from_proc_invalid_format() {
298 let pid = std::process::id() as i32;
299 let timestamp = Instant::now();
300 let result = ProcessStats::from_proc(pid, timestamp);
301 assert!(result.is_ok());
302 }
303}