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!(
101 "failed to retrieve info for pid={curr_proc_pid}, process probably died between snapshots"
102 );
103 continue;
104 };
105 let cwd = curr_proc.cwd().unwrap_or_default();
106
107 let curr_io = curr_proc.io().ok();
108 let curr_stat = curr_proc.stat().ok();
109 let curr_status = curr_proc.status().ok();
110 let curr_time = Instant::now();
111 let interval = curr_time.saturating_duration_since(prev_time);
112 let ppid = curr_proc.stat().map(|p| p.ppid).unwrap_or_default();
113 let curr_proc = ProcessTask::Process(curr_proc);
114
115 let proc = ProcessInfo {
116 pid,
117 ppid,
118 curr_proc,
119 curr_io,
120 prev_io,
121 curr_stat,
122 prev_stat,
123 curr_status,
124 interval,
125 cwd,
126 };
127
128 ret.push(proc);
129 }
130
131 ret
132}
133
134impl ProcessInfo {
135 pub fn pid(&self) -> i32 {
137 self.pid
138 }
139
140 pub fn ppid(&self) -> i32 {
142 self.ppid
143 }
144
145 pub fn name(&self) -> String {
147 if let Some(name) = self.comm() {
148 return name;
149 }
150 if let Ok(mut cmd) = self.curr_proc.cmdline() {
152 if let Some(name) = cmd.first_mut() {
153 return std::mem::take(name);
156 }
157 }
158 String::new()
159 }
160
161 pub fn command(&self) -> String {
167 if let Ok(cmd) = self.curr_proc.cmdline() {
168 if !cmd.is_empty() {
170 return cmd.join(" ").replace(['\n', '\t'], " ");
171 }
172 }
173 self.comm().unwrap_or_default()
174 }
175
176 pub fn cwd(&self) -> String {
177 self.cwd.display().to_string()
178 }
179
180 pub fn status(&self) -> String {
182 if let Ok(p) = self.curr_proc.stat() {
183 match p.state {
184 'S' => "Sleeping",
185 'R' => "Running",
186 'D' => "Disk sleep",
187 'Z' => "Zombie",
188 'T' => "Stopped",
189 't' => "Tracing",
190 'X' => "Dead",
191 'x' => "Dead",
192 'K' => "Wakekill",
193 'W' => "Waking",
194 'P' => "Parked",
195 _ => "Unknown",
196 }
197 } else {
198 "Unknown"
199 }
200 .into()
201 }
202
203 pub fn cpu_usage(&self) -> f64 {
205 if let Some(cs) = &self.curr_stat {
206 if let Some(ps) = &self.prev_stat {
207 let curr_time = cs.utime + cs.stime;
208 let prev_time = ps.utime + ps.stime;
209
210 let usage_ms =
211 curr_time.saturating_sub(prev_time) * 1000 / procfs::ticks_per_second();
212 let interval_ms =
213 self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis());
214 usage_ms as f64 * 100.0 / interval_ms as f64
215 } else {
216 0.0
217 }
218 } else {
219 0.0
220 }
221 }
222
223 pub fn mem_size(&self) -> u64 {
225 match self.curr_proc.stat() {
226 Ok(p) => p.rss_bytes().get(),
227 Err(_) => 0,
228 }
229 }
230
231 pub fn virtual_size(&self) -> u64 {
233 self.curr_proc.stat().map(|p| p.vsize).unwrap_or_default()
234 }
235
236 fn comm(&self) -> Option<String> {
237 self.curr_proc.stat().map(|st| st.comm).ok()
238 }
239}