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