1use std::{
2 collections::BTreeMap,
3 fs,
4 hash::{Hash, Hasher},
5 io::ErrorKind,
6 mem::take,
7 path::Path,
8 thread::sleep,
9 time::Duration,
10};
11
12use chrono::prelude::*;
13
14use crate::{
15 btime::get_btime,
16 cpu::get_average_cpu_stat,
17 process::{
18 get_process_stat, get_process_status, get_process_time_stat, ProcessFilter, ProcessStat,
19 ProcessState, ProcessTimeStat,
20 },
21 scanner_rust::ScannerError,
22};
23
24#[derive(Debug, Clone, Eq)]
25pub struct Process {
26 pub pid: u32,
27 pub effective_uid: u32,
28 pub effective_gid: u32,
29 pub state: ProcessState,
30 pub ppid: u32,
31 pub program: String,
32 pub cmdline: String,
33 pub tty: Option<String>,
34 pub priority: i8,
35 pub real_time_priority: Option<u8>,
36 pub nice: i8,
37 pub threads: usize,
38 pub vsz: usize,
40 pub rss: usize,
42 pub rss_shared: usize,
44 pub rss_anon: usize,
46 pub start_time: DateTime<Utc>,
47}
48
49impl Hash for Process {
50 #[inline]
51 fn hash<H: Hasher>(&self, state: &mut H) {
52 self.pid.hash(state)
53 }
54}
55
56impl PartialEq for Process {
57 #[inline]
58 fn eq(&self, other: &Process) -> bool {
59 self.pid.eq(&other.pid)
60 }
61}
62
63fn get_process_with_stat_inner<P: AsRef<Path>>(
64 pid: u32,
65 process_path: P,
66 process_filter: &ProcessFilter,
67) -> Result<Option<(Process, ProcessStat)>, ScannerError> {
68 let process_path = process_path.as_ref();
69
70 let mut program_filter_match = true;
71
72 let status = get_process_status(pid)?;
73
74 if let Some(uid_filter) = process_filter.uid_filter {
75 if status.real_uid != uid_filter
76 && status.effective_uid != uid_filter
77 && status.saved_set_uid != uid_filter
78 && status.fs_uid != uid_filter
79 {
80 return Ok(None);
81 }
82 }
83
84 if let Some(gid_filter) = process_filter.gid_filter {
85 if status.real_gid != gid_filter
86 && status.effective_gid != gid_filter
87 && status.saved_set_gid != gid_filter
88 && status.fs_gid != gid_filter
89 {
90 return Ok(None);
91 }
92 }
93
94 let cmdline = {
95 let mut data = fs::read(process_path.join("cmdline"))?;
96
97 for e in data.iter_mut() {
98 if *e == 0 {
99 *e = b' ';
100 }
101 }
102
103 unsafe { String::from_utf8_unchecked(data) }
104 };
105
106 if let Some(program_filter) = process_filter.program_filter.as_ref() {
107 if !program_filter.is_match(&cmdline) {
108 program_filter_match = false;
109 }
110 }
111
112 let mut stat = get_process_stat(pid)?;
113
114 if !program_filter_match {
115 if let Some(program_filter) = process_filter.program_filter.as_ref() {
116 if !program_filter.is_match(&stat.comm) {
117 return Ok(None);
118 }
119 }
120 }
121
122 let effective_uid = status.effective_uid;
123 let effective_gid = status.effective_gid;
124 let state = stat.state;
125 let ppid = stat.ppid;
126 let program = take(&mut stat.comm);
127
128 let tty = {
129 match stat.tty_nr_major {
130 4 => {
131 if stat.tty_nr_minor < 64 {
132 Some(format!("tty{}", stat.tty_nr_minor))
133 } else {
134 Some(format!("ttyS{}", stat.tty_nr_minor - 64))
135 }
136 },
137 136..=143 => Some(format!("pts/{}", stat.tty_nr_minor)),
138 _ => None,
139 }
140 };
141
142 if let Some(tty_filter) = process_filter.tty_filter.as_ref() {
143 match tty.as_ref() {
144 Some(tty) => {
145 if !tty_filter.is_match(tty) {
146 return Ok(None);
147 }
148 },
149 None => return Ok(None),
150 }
151 }
152
153 let priority = stat.priority;
154 let real_time_priority = if stat.rt_priority > 0 { Some(stat.rt_priority) } else { None };
155 let nice = stat.nice;
156 let threads = stat.num_threads;
157 let vsz = stat.vsize;
158 let rss = stat.rss;
159 let rss_shared = stat.shared;
160 let rss_anon = stat.rss_anon;
161
162 let start_time =
163 get_btime() + chrono::Duration::from_std(Duration::from_millis(stat.starttime)).unwrap();
164
165 let process = Process {
166 pid,
167 effective_uid,
168 effective_gid,
169 state,
170 ppid,
171 program,
172 cmdline,
173 tty,
174 priority,
175 real_time_priority,
176 nice,
177 threads,
178 vsz,
179 rss,
180 rss_shared,
181 rss_anon,
182 start_time,
183 };
184
185 Ok(Some((process, stat)))
186}
187
188#[inline]
198pub fn get_process_with_stat(pid: u32) -> Result<(Process, ProcessStat), ScannerError> {
199 let process_path = Path::new("/proc").join(pid.to_string());
200
201 get_process_with_stat_inner(pid, process_path, &ProcessFilter::default()).map(|o| o.unwrap())
202}
203
204pub fn get_processes_with_stat(
216 process_filter: &ProcessFilter,
217) -> Result<Vec<(Process, ProcessStat)>, ScannerError> {
218 let mut processes_with_stats = Vec::new();
219
220 let proc = Path::new("/proc");
221
222 if let Some(pid_filter) = process_filter.pid_filter.as_ref().copied() {
223 let mut pid_ppid_map: BTreeMap<u32, u32> = BTreeMap::new();
224
225 for dir_entry in proc.read_dir()? {
226 let dir_entry = dir_entry?;
227
228 if let Some(file_name) = dir_entry.file_name().to_str() {
229 if let Ok(pid) = file_name.parse::<u32>() {
230 let process_path = dir_entry.path();
231
232 match get_process_with_stat_inner(pid, process_path, process_filter) {
233 Ok(r) => {
234 if let Some((process, stat)) = r {
235 if pid != pid_filter && process.ppid != pid_filter {
236 let mut not_related = true;
237
238 let mut p_ppid = pid_ppid_map.get(&process.ppid);
239
240 while let Some(ppid) = p_ppid.copied() {
241 if ppid == pid_filter {
242 not_related = false;
243
244 break;
245 }
246
247 p_ppid = pid_ppid_map.get(&ppid);
248 }
249
250 if not_related {
251 continue;
252 }
253 }
254
255 pid_ppid_map.insert(pid, process.ppid);
256
257 processes_with_stats.push((process, stat));
258 }
259 },
260 Err(err) => {
261 if let ScannerError::IOError(err) = &err {
262 if err.kind() == ErrorKind::NotFound {
263 continue;
264 }
265 }
266
267 return Err(err);
268 },
269 }
270 }
271 }
272 }
273 } else {
274 for dir_entry in proc.read_dir()? {
275 let dir_entry = dir_entry?;
276
277 if let Some(file_name) = dir_entry.file_name().to_str() {
278 if let Ok(pid) = file_name.parse::<u32>() {
279 let process_path = dir_entry.path();
280
281 match get_process_with_stat_inner(pid, process_path, process_filter) {
282 Ok(r) => {
283 if let Some((process, stat)) = r {
284 processes_with_stats.push((process, stat));
285 }
286 },
287 Err(err) => {
288 if let ScannerError::IOError(err) = &err {
289 if err.kind() == ErrorKind::NotFound {
290 continue;
291 }
292 }
293
294 return Err(err);
295 },
296 }
297 }
298 }
299 }
300 }
301
302 Ok(processes_with_stats)
303}
304
305pub fn get_processes_with_cpu_utilization_in_percentage(
324 process_filter: &ProcessFilter,
325 interval: Duration,
326) -> Result<Vec<(Process, f64)>, ScannerError> {
327 let pre_average_cpu_stat = get_average_cpu_stat()?;
328 let processes_with_stat = get_processes_with_stat(process_filter).unwrap();
329
330 let mut processes_with_cpu_percentage = Vec::with_capacity(processes_with_stat.len());
331
332 sleep(interval);
333
334 let average_cpu_stat = get_average_cpu_stat()?;
335
336 let total_cpu_time_f64 = {
337 let pre_average_cpu_time = pre_average_cpu_stat.compute_cpu_time();
338 let average_cpu_time = average_cpu_stat.compute_cpu_time();
339
340 (average_cpu_time.get_total_time() - pre_average_cpu_time.get_total_time()) as f64
341 };
342
343 for (process, pre_process_stat) in processes_with_stat {
344 if let Ok(process_time_stat) = get_process_time_stat(process.pid) {
345 let pre_process_time_stat: ProcessTimeStat = pre_process_stat.into();
346
347 let cpu_percentage = pre_process_time_stat
348 .compute_cpu_utilization_in_percentage(&process_time_stat, total_cpu_time_f64);
349
350 processes_with_cpu_percentage.push((process, cpu_percentage));
351 }
352 }
353
354 Ok(processes_with_cpu_percentage)
355}