1use crate::error::{ProcError, Result};
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9use sysinfo::{Pid, ProcessStatus as SysProcessStatus, System};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ProcessStatus {
15 Running,
16 Sleeping,
17 Stopped,
18 Zombie,
19 Dead,
20 Unknown,
21}
22
23impl From<SysProcessStatus> for ProcessStatus {
24 fn from(status: SysProcessStatus) -> Self {
25 match status {
26 SysProcessStatus::Run => ProcessStatus::Running,
27 SysProcessStatus::Sleep => ProcessStatus::Sleeping,
28 SysProcessStatus::Stop => ProcessStatus::Stopped,
29 SysProcessStatus::Zombie => ProcessStatus::Zombie,
30 SysProcessStatus::Dead => ProcessStatus::Dead,
31 _ => ProcessStatus::Unknown,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Process {
39 pub pid: u32,
41 pub name: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub exe_path: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub cwd: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub command: Option<String>,
52 pub cpu_percent: f32,
54 pub memory_mb: f64,
56 pub status: ProcessStatus,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub user: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub parent_pid: Option<u32>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub start_time: Option<u64>,
67}
68
69impl Process {
70 pub fn find_by_name(pattern: &str) -> Result<Vec<Process>> {
72 let mut sys = System::new_all();
73 sys.refresh_all();
74
75 let pattern_lower = pattern.to_lowercase();
76 let processes: Vec<Process> = sys
77 .processes()
78 .iter()
79 .filter_map(|(pid, proc)| {
80 let name = proc.name().to_string_lossy().to_string();
81 let cmd: String = proc
82 .cmd()
83 .iter()
84 .map(|s| s.to_string_lossy())
85 .collect::<Vec<_>>()
86 .join(" ");
87
88 if name.to_lowercase().contains(&pattern_lower)
90 || cmd.to_lowercase().contains(&pattern_lower)
91 {
92 Some(Process::from_sysinfo(*pid, proc))
93 } else {
94 None
95 }
96 })
97 .collect();
98
99 if processes.is_empty() {
100 return Err(ProcError::ProcessNotFound(pattern.to_string()));
101 }
102
103 Ok(processes)
104 }
105
106 pub fn find_by_pid(pid: u32) -> Result<Option<Process>> {
108 let mut sys = System::new_all();
109 sys.refresh_all();
110
111 let sysinfo_pid = Pid::from_u32(pid);
112
113 Ok(sys
114 .processes()
115 .get(&sysinfo_pid)
116 .map(|proc| Process::from_sysinfo(sysinfo_pid, proc)))
117 }
118
119 pub fn find_all() -> Result<Vec<Process>> {
121 let mut sys = System::new_all();
122 sys.refresh_all();
123
124 let processes: Vec<Process> = sys
125 .processes()
126 .iter()
127 .map(|(pid, proc)| Process::from_sysinfo(*pid, proc))
128 .collect();
129
130 Ok(processes)
131 }
132
133 pub fn find_stuck(timeout: Duration) -> Result<Vec<Process>> {
136 let mut sys = System::new_all();
137 sys.refresh_all();
138
139 std::thread::sleep(Duration::from_millis(500));
141 sys.refresh_all();
142
143 let timeout_secs = timeout.as_secs();
144 let processes: Vec<Process> = sys
145 .processes()
146 .iter()
147 .filter_map(|(pid, proc)| {
148 let cpu = proc.cpu_usage();
149 let run_time = proc.run_time();
150
151 if run_time > timeout_secs && cpu > 50.0 {
154 Some(Process::from_sysinfo(*pid, proc))
155 } else {
156 None
157 }
158 })
159 .collect();
160
161 Ok(processes)
162 }
163
164 pub fn kill(&self) -> Result<()> {
166 let mut sys = System::new();
167 sys.refresh_processes(
168 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
169 true,
170 );
171
172 if let Some(proc) = sys.process(Pid::from_u32(self.pid)) {
173 if proc.kill() {
174 Ok(())
175 } else {
176 Err(ProcError::SignalError(format!(
177 "Failed to kill process {}",
178 self.pid
179 )))
180 }
181 } else {
182 Err(ProcError::ProcessNotFound(self.pid.to_string()))
183 }
184 }
185
186 pub fn kill_and_wait(&self) -> Result<Option<std::process::ExitStatus>> {
189 let mut sys = System::new();
190 sys.refresh_processes(
191 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
192 true,
193 );
194
195 if let Some(proc) = sys.process(Pid::from_u32(self.pid)) {
196 proc.kill_and_wait().map_err(|e| {
197 ProcError::SignalError(format!("Failed to kill process {}: {:?}", self.pid, e))
198 })
199 } else {
200 Err(ProcError::ProcessNotFound(self.pid.to_string()))
201 }
202 }
203
204 #[cfg(unix)]
206 pub fn terminate(&self) -> Result<()> {
207 use nix::sys::signal::{kill, Signal};
208 use nix::unistd::Pid as NixPid;
209
210 kill(NixPid::from_raw(self.pid as i32), Signal::SIGTERM)
211 .map_err(|e| ProcError::SignalError(e.to_string()))
212 }
213
214 #[cfg(windows)]
216 pub fn terminate(&self) -> Result<()> {
217 use std::process::Command;
218
219 Command::new("taskkill")
220 .args(["/PID", &self.pid.to_string()])
221 .output()
222 .map_err(|e| ProcError::SystemError(e.to_string()))?;
223
224 Ok(())
225 }
226
227 pub fn exists(&self) -> bool {
229 let mut sys = System::new();
230 sys.refresh_processes(
231 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
232 true,
233 );
234 sys.process(Pid::from_u32(self.pid)).is_some()
235 }
236
237 pub fn is_running(&self) -> bool {
239 self.exists()
240 }
241
242 pub fn wait(&self) -> Option<std::process::ExitStatus> {
245 let mut sys = System::new();
246 sys.refresh_processes(
247 sysinfo::ProcessesToUpdate::Some(&[Pid::from_u32(self.pid)]),
248 true,
249 );
250
251 sys.process(Pid::from_u32(self.pid))
252 .and_then(|proc| proc.wait())
253 }
254
255 fn from_sysinfo(pid: Pid, proc: &sysinfo::Process) -> Self {
257 let cmd_vec = proc.cmd();
258 let command = if cmd_vec.is_empty() {
259 None
260 } else {
261 Some(
262 cmd_vec
263 .iter()
264 .map(|s| s.to_string_lossy())
265 .collect::<Vec<_>>()
266 .join(" "),
267 )
268 };
269
270 let exe_path = proc.exe().map(|p| p.to_string_lossy().to_string());
271 let cwd = proc.cwd().map(|p| p.to_string_lossy().to_string());
272
273 Process {
274 pid: pid.as_u32(),
275 name: proc.name().to_string_lossy().to_string(),
276 exe_path,
277 cwd,
278 command,
279 cpu_percent: proc.cpu_usage(),
280 memory_mb: proc.memory() as f64 / 1024.0 / 1024.0,
281 status: ProcessStatus::from(proc.status()),
282 user: proc.user_id().map(|u| u.to_string()),
283 parent_pid: proc.parent().map(|p| p.as_u32()),
284 start_time: Some(proc.start_time()),
285 }
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_find_all_processes() {
295 let processes = Process::find_all().unwrap();
296 assert!(!processes.is_empty(), "Should find at least one process");
297 }
298
299 #[test]
300 fn test_find_by_pid_self() {
301 let pid = std::process::id();
302 let process = Process::find_by_pid(pid).unwrap();
303 assert!(process.is_some(), "Should find own process");
304 }
305
306 #[test]
307 fn test_find_nonexistent_process() {
308 let result = Process::find_by_name("nonexistent_process_12345");
309 assert!(result.is_err());
310 }
311}