1use log::info;
2use procfs::process::{FDInfo, Io, Process, Stat, Status};
3use procfs::{ProcError, ProcessCGroups, WithCurrentSystemInfo};
4use std::path::PathBuf;
5use std::thread;
6use std::time::{Duration, Instant};
7
8pub enum ProcessTask {
9 Process(Process),
10 Task { stat: Box<Stat>, owner: u32 },
11}
12
13impl ProcessTask {
14 pub fn stat(&self) -> Result<Stat, ProcError> {
15 match self {
16 ProcessTask::Process(x) => x.stat(),
17 ProcessTask::Task { stat: x, owner: _ } => Ok(*x.clone()),
18 }
19 }
20
21 pub fn cmdline(&self) -> Result<Vec<String>, ProcError> {
22 match self {
23 ProcessTask::Process(x) => x.cmdline(),
24 _ => Err(ProcError::Other("not supported".to_string())),
25 }
26 }
27
28 pub fn cgroups(&self) -> Result<ProcessCGroups, ProcError> {
29 match self {
30 ProcessTask::Process(x) => x.cgroups(),
31 _ => Err(ProcError::Other("not supported".to_string())),
32 }
33 }
34
35 pub fn fd(&self) -> Result<Vec<FDInfo>, ProcError> {
36 match self {
37 ProcessTask::Process(x) => x.fd()?.collect(),
38 _ => Err(ProcError::Other("not supported".to_string())),
39 }
40 }
41
42 pub fn loginuid(&self) -> Result<u32, ProcError> {
43 match self {
44 ProcessTask::Process(x) => x.loginuid(),
45 _ => Err(ProcError::Other("not supported".to_string())),
46 }
47 }
48
49 pub fn owner(&self) -> u32 {
50 match self {
51 ProcessTask::Process(x) => x.uid().unwrap_or(0),
52 ProcessTask::Task { stat: _, owner: x } => *x,
53 }
54 }
55
56 pub fn wchan(&self) -> Result<String, ProcError> {
57 match self {
58 ProcessTask::Process(x) => x.wchan(),
59 _ => Err(ProcError::Other("not supported".to_string())),
60 }
61 }
62}
63
64pub struct ProcessInfo {
65 pub pid: i32,
66 pub ppid: i32,
67 pub curr_proc: ProcessTask,
68 pub curr_io: Option<Io>,
69 pub prev_io: Option<Io>,
70 pub curr_stat: Option<Stat>,
71 pub prev_stat: Option<Stat>,
72 pub curr_status: Option<Status>,
73 pub interval: Duration,
74 pub cwd: PathBuf,
75}
76
77pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec<ProcessInfo> {
78 let mut base_procs = Vec::new();
79 let mut ret = Vec::new();
80
81 if let Ok(all_proc) = procfs::process::all_processes() {
83 for proc in all_proc.flatten() {
84 let io = proc.io().ok();
85 let stat = proc.stat().ok();
86 let time = Instant::now();
87 base_procs.push((proc.pid(), io, stat, time));
88 }
89 }
90
91 thread::sleep(interval);
93
94 for (pid, prev_io, prev_stat, prev_time) in base_procs {
96 let curr_proc_pid = pid;
97 let curr_proc = if let Ok(p) = Process::new(curr_proc_pid) {
98 p
99 } else {
100 info!("failed to retrieve info for pid={curr_proc_pid}, process probably died between snapshots");
101 continue;
102 };
103 let cwd = curr_proc.cwd().unwrap_or_default();
104
105 let curr_io = curr_proc.io().ok();
106 let curr_stat = curr_proc.stat().ok();
107 let curr_status = curr_proc.status().ok();
108 let curr_time = Instant::now();
109 let interval = curr_time.saturating_duration_since(prev_time);
110 let ppid = curr_proc.stat().map(|p| p.ppid).unwrap_or_default();
111 let curr_proc = ProcessTask::Process(curr_proc);
112
113 let proc = ProcessInfo {
114 pid,
115 ppid,
116 curr_proc,
117 curr_io,
118 prev_io,
119 curr_stat,
120 prev_stat,
121 curr_status,
122 interval,
123 cwd,
124 };
125
126 ret.push(proc);
127 }
128
129 ret
130}
131
132impl ProcessInfo {
133 pub fn pid(&self) -> i32 {
135 self.pid
136 }
137
138 pub fn ppid(&self) -> i32 {
140 self.ppid
141 }
142
143 pub fn name(&self) -> String {
145 if let Some(name) = self.comm() {
146 return name;
147 }
148 if let Ok(mut cmd) = self.curr_proc.cmdline() {
150 if let Some(name) = cmd.first_mut() {
151 return std::mem::take(name);
154 }
155 }
156 String::new()
157 }
158
159 pub fn command(&self) -> String {
165 if let Ok(cmd) = self.curr_proc.cmdline() {
166 if !cmd.is_empty() {
168 return cmd.join(" ").replace(['\n', '\t'], " ");
169 }
170 }
171 self.comm().unwrap_or_default()
172 }
173
174 pub fn cwd(&self) -> String {
175 self.cwd.display().to_string()
176 }
177
178 pub fn status(&self) -> String {
180 if let Ok(p) = self.curr_proc.stat() {
181 match p.state {
182 'S' => "Sleeping",
183 'R' => "Running",
184 'D' => "Disk sleep",
185 'Z' => "Zombie",
186 'T' => "Stopped",
187 't' => "Tracing",
188 'X' => "Dead",
189 'x' => "Dead",
190 'K' => "Wakekill",
191 'W' => "Waking",
192 'P' => "Parked",
193 _ => "Unknown",
194 }
195 } else {
196 "Unknown"
197 }
198 .into()
199 }
200
201 pub fn cpu_usage(&self) -> f64 {
203 if let Some(cs) = &self.curr_stat {
204 if let Some(ps) = &self.prev_stat {
205 let curr_time = cs.utime + cs.stime;
206 let prev_time = ps.utime + ps.stime;
207
208 let usage_ms =
209 curr_time.saturating_sub(prev_time) * 1000 / procfs::ticks_per_second();
210 let interval_ms =
211 self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis());
212 usage_ms as f64 * 100.0 / interval_ms as f64
213 } else {
214 0.0
215 }
216 } else {
217 0.0
218 }
219 }
220
221 pub fn mem_size(&self) -> u64 {
223 match self.curr_proc.stat() {
224 Ok(p) => p.rss_bytes().get(),
225 Err(_) => 0,
226 }
227 }
228
229 pub fn virtual_size(&self) -> u64 {
231 self.curr_proc.stat().map(|p| p.vsize).unwrap_or_default()
232 }
233
234 fn comm(&self) -> Option<String> {
235 self.curr_proc.stat().map(|st| st.comm).ok()
236 }
237}