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