1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use crate::row::{CpuTicks, Row, RowFlags};
7use crate::system::{CpuTimes, DiskCounters, MemInfo, NetCounters, SwapInfo, SystemSnapshot};
8use crate::table::Cache;
9use crate::Pid;
10
11#[cfg(target_os = "linux")]
12use super::delayacct::{DelayAcct, DelayStats};
13use super::scan;
14use super::LinuxPlatform;
15
16const PAR_THRESHOLD: usize = 512;
18
19const CACHE_SLACK: usize = 4;
21
22#[derive(Copy, Clone)]
24struct PrevRow {
25 starttime: u64,
26 proc_ticks: u64,
27}
28
29pub struct Sampler {
30 root: PathBuf,
31 clk_tck: u64,
32 page_size: u64,
33 cache: Cache,
34 prev_cpu: Vec<CpuTimes>,
36 prev_rows: HashMap<Pid, PrevRow>,
38 prev_net: Option<(NetCounters, std::time::Instant)>,
44 prev_disk: Option<(DiskCounters, std::time::Instant)>,
45 pub use_rayon: bool,
46 show_threads: bool,
52 read_smaps: bool,
59 read_gpu: bool,
61 #[cfg(target_os = "linux")]
67 read_delayacct: bool,
68 #[cfg(target_os = "linux")]
75 delayacct: Option<DelayAcct>,
76 #[cfg(target_os = "linux")]
82 prev_delay: HashMap<Pid, (u64, DelayStats)>,
83}
84
85impl Default for Sampler {
86 fn default() -> Self {
87 Self::new(Path::new("/proc"))
88 }
89}
90
91impl Sampler {
92 pub fn new(root: &Path) -> Self {
93 Self {
94 root: root.to_path_buf(),
95 clk_tck: LinuxPlatform::clk_tck(),
96 page_size: LinuxPlatform::page_size(),
97 cache: Cache::new(),
98 prev_cpu: Vec::new(),
99 prev_rows: HashMap::new(),
100 prev_net: None,
101 prev_disk: None,
102 use_rayon: true,
103 show_threads: false,
104 read_smaps: false,
105 read_gpu: false,
106 #[cfg(target_os = "linux")]
107 read_delayacct: false,
108 #[cfg(target_os = "linux")]
109 delayacct: None,
110 #[cfg(target_os = "linux")]
111 prev_delay: HashMap::new(),
112 }
113 }
114
115 #[cfg(target_os = "linux")]
126 fn delay_for(&mut self, pid: Pid, starttime: u64) -> Option<DelayStats> {
127 let acct = match self.delayacct {
128 Some(ref mut a) => a,
129 None => self.delayacct.insert(DelayAcct::open()?),
130 };
131 let stats = acct.read(pid)?;
132 if let Some(&(prev_start, _)) = self.prev_delay.get(&pid) {
135 if prev_start != starttime {
136 self.prev_delay.remove(&pid);
137 }
138 }
139 self.prev_delay.insert(pid, (starttime, stats));
140 Some(stats)
141 }
142
143 pub fn clk_tck(&self) -> u64 {
144 self.clk_tck
145 }
146
147 fn sample_inner(&mut self) -> (SystemSnapshot, Vec<Row>) {
149 let at = std::time::Instant::now();
150
151 let cpu = scan::read_proc_stat(&self.root);
152 let cpu_delta: Vec<CpuTimes> = if self.prev_cpu.len() == cpu.len() && !cpu.is_empty() {
153 cpu.iter()
154 .zip(&self.prev_cpu)
155 .map(|(n, p)| n.delta(p))
156 .collect()
157 } else {
158 vec![CpuTimes::default(); cpu.len()]
160 };
161
162 let mut mem = MemInfo::default();
163 let mut swap = SwapInfo::default();
164 scan::read_meminfo(&self.root, &mut mem, &mut swap);
165
166 let load = scan::read_loadavg(&self.root).unwrap_or([0.0; 3]);
167 let uptime = scan::read_uptime(&self.root).unwrap_or(0);
168
169 let psi = scan::read_psi(&self.root);
174 let zram = scan::read_zram(&self.root);
175 if let Some(percent) = scan::read_zswap_max_percent(&self.root) {
178 mem.zswap_max = mem.total.saturating_mul(percent.min(100)) / 100;
179 }
180
181 let net = scan::read_net_counters(&self.root);
186 let disk = scan::read_disk_counters(&self.root);
187 let net_rate = match self.prev_net {
188 Some((prev, at_prev)) => net.per_sec(&prev, at.duration_since(at_prev).as_secs_f64()),
189 None => crate::system::Rate::default(),
190 };
191 let disk_rate = match self.prev_disk {
192 Some((prev, at_prev)) => {
193 disk.per_sec(&prev, at.duration_since(at_prev).as_secs_f64())
194 }
195 None => crate::system::Rate::default(),
196 };
197 self.prev_net = Some((net, at));
198 self.prev_disk = Some((disk, at));
199
200 let pids = scan::list_pids(&self.root).unwrap_or_default();
201 let mut rows = self.read_rows(&pids, &cpu_delta, uptime, mem.total);
202 if self.show_threads {
203 let threads = self.read_threads(&pids, &cpu_delta);
204 rows.extend(threads);
205 }
206
207 self.prev_cpu = cpu.clone();
208 self.cache.retain_live(
209 &rows
210 .iter()
211 .map(|r| (r.pid, r.starttime))
212 .collect::<Vec<_>>(),
213 );
214 if self.cache.len() > rows.len().saturating_mul(CACHE_SLACK) + 64 {
215 self.cache.clear();
216 }
217
218 let snapshot = SystemSnapshot {
219 at,
220 cpu,
221 cpu_delta,
222 mem,
223 swap,
224 load,
225 uptime,
226 clk_tck: self.clk_tck,
227 page_size: self.page_size,
228 net,
229 net_rate,
230 disk,
231 disk_rate,
232 tasks: task_stats(&rows),
233 psi,
234 zram,
235 };
236 (snapshot, rows)
237 }
238
239 fn read_threads(&mut self, pids: &[Pid], cpu_delta: &[CpuTimes]) -> Vec<Row> {
246 let root = self.root.clone();
247 let page_size = self.page_size;
248 let total_delta = cpu_delta.first().map(|d| d.total()).unwrap_or(0) as f32;
249
250 let mut pairs: Vec<(Pid, Pid)> = Vec::new();
252 for &pid in pids {
253 let Ok(tids) = scan::list_tids(&root, pid) else {
254 continue;
255 };
256 for tid in tids {
257 if tid != pid {
258 pairs.push((pid, tid));
259 }
260 }
261 }
262 if pairs.is_empty() {
263 return Vec::new();
264 }
265
266 let read_one = |&(pid, tid): &(Pid, Pid)| {
267 scan::read_thread_stat(&root, pid, tid).map(|(comm, fields)| (pid, tid, comm, fields))
268 };
269
270 let stats: Vec<(Pid, Pid, crate::row::CommBuf, super::parse::StatFields)> =
271 if self.use_rayon && pairs.len() >= PAR_THRESHOLD {
272 use rayon::prelude::*;
273 pairs.par_iter().filter_map(read_one).collect()
274 } else {
275 pairs.iter().filter_map(read_one).collect()
276 };
277
278 let mut out = Vec::with_capacity(stats.len());
279 for (pid, tid, comm, fields) in stats {
280 let starttime = fields.starttime;
281 let mut row = Row::default_for_test(tid);
282 row.pid = tid;
283 row.thread_id = Some(tid as u64);
288 row.tgid = pid;
289 row.is_thread = true;
290 row.ppid = pid;
291 row.comm = comm;
292
293 let prev_thread_ticks = match self.prev_rows.get(&tid) {
294 Some(p) if p.starttime == starttime => p.proc_ticks,
295 _ => 0,
296 };
297 self.prev_rows.insert(
298 tid,
299 PrevRow {
300 starttime,
301 proc_ticks: fields.cpu_ticks(),
302 },
303 );
304
305 scan::apply_stat(&mut row, &fields, page_size);
306
307 let delta = fields.cpu_ticks().saturating_sub(prev_thread_ticks) as f32;
308 row.percent_cpu = if total_delta > 0.0 {
309 delta * 100.0 / total_delta
310 } else {
311 0.0
312 };
313
314 let stable = scan::read_identity(&self.root, pid, starttime, &mut self.cache);
316 row.uid = stable.uid;
317 row.cmdline = stable.cmdline.clone();
318 row.exe = stable.exe.clone();
319 row.cwd = stable.cwd.clone();
320 row.cgroup = stable.cgroup.clone();
321 #[cfg(target_os = "linux")]
322 {
323 row.container = stable.container.clone();
324 row.container_runtime = stable.container_runtime.clone();
325 }
326 row.flags = row
327 .flags
328 .insert(RowFlags::USERLAND_THREAD)
329 .set(RowFlags::STALE_EXE, stable.exe_deleted);
330 row.nlwp = 1;
331
332 out.push(row);
333 }
334 out
335 }
336
337 fn read_rows(
339 &mut self,
340 pids: &[Pid],
341 cpu_delta: &[CpuTimes],
342 uptime: u64,
343 mem_total: u64,
344 ) -> Vec<Row> {
345 let root = &self.root;
346 let page_size = self.page_size;
347
348 let stats: Vec<(Pid, crate::row::CommBuf, super::parse::StatFields)> =
350 if self.use_rayon && pids.len() >= PAR_THRESHOLD {
351 use rayon::prelude::*;
352 pids.par_iter()
353 .filter_map(|&pid| scan::read_stat(root, pid).map(|(c, f)| (pid, c, f)))
354 .collect()
355 } else {
356 pids.iter()
357 .filter_map(|&pid| scan::read_stat(root, pid).map(|(c, f)| (pid, c, f)))
358 .collect()
359 };
360
361 let mut out = Vec::with_capacity(stats.len());
363 let mut next_prev = HashMap::with_capacity(stats.len());
364 let total_delta = cpu_delta.first().map(|d| d.total()).unwrap_or(0) as f32;
365
366 for (pid, comm, fields) in stats {
367 let starttime = fields.starttime;
368 let mut row = Row::default_for_test(pid);
369
370 let prev_proc_ticks = match self.prev_rows.get(&pid) {
373 Some(p) if p.starttime == starttime => p.proc_ticks,
374 _ => 0,
375 };
376 next_prev.insert(
377 pid,
378 PrevRow {
379 starttime,
380 proc_ticks: fields.cpu_ticks(),
381 },
382 );
383
384 scan::apply_stat(&mut row, &fields, page_size);
385 row.comm = comm;
386
387 #[cfg(target_os = "linux")]
391 if self.read_smaps {
392 if let Some(rollup) = super::fsops::read_smaps_rollup(&self.root, pid) {
393 row.pss = Some(rollup.pss);
394 row.epss = Some(rollup.epss());
395 row.private = Some(rollup.private());
396 row.swap_bytes = Some(rollup.swap);
397 }
398 }
399 #[cfg(target_os = "linux")]
402 if self.read_gpu {
403 if let Some(gpu) = super::scan::read_gpu_usage(&self.root, pid) {
404 row.gpu_percent = gpu.percent();
405 row.gpu_ns = Some(gpu.gpu_ns);
406 }
407 }
408 #[cfg(target_os = "linux")]
414 if self.read_delayacct {
415 if let Some(stats) = self.delay_for(pid, starttime) {
416 row.cpu_delay_percent = stats.cpu_percent();
417 row.io_delay_percent = stats.blkio_percent();
418 row.swap_delay_percent = stats.swapin_percent();
419 }
420 }
421
422 let d_proc = fields.cpu_ticks().saturating_sub(prev_proc_ticks) as f32;
423 row.percent_cpu = if total_delta > 0.0 {
424 d_proc * 100.0 / total_delta
426 } else {
427 0.0
428 };
429
430 let stable = scan::read_identity(&self.root, pid, starttime, &mut self.cache);
431 row.uid = stable.uid;
432 row.flags = row
433 .flags
434 .set(RowFlags::KERNEL_THREAD, stable.is_kernel_thread);
435 row.cmdline = stable.cmdline;
436 row.exe = stable.exe;
437 row.cwd = stable.cwd;
438 row.cgroup = stable.cgroup;
439 row.flags = row
443 .flags
444 .set(RowFlags::STALE_EXE, stable.exe_deleted);
445 #[cfg(target_os = "linux")]
446 {
447 row.container = stable.container;
448 row.container_runtime = stable.container_runtime;
449 }
450
451 row.elapsed = if self.clk_tck > 0 {
452 uptime.saturating_sub(starttime.checked_div(self.clk_tck).unwrap_or(0))
453 } else {
454 0
455 };
456 row.percent_mem = if mem_total > 0 {
457 row.res as f32 * 100.0 / mem_total as f32
458 } else {
459 0.0
460 };
461
462 row.ticks = CpuTicks {
463 proc: fields.cpu_ticks(),
464 total: total_delta as u64,
465 };
466 row.prev_ticks = CpuTicks {
467 proc: prev_proc_ticks,
468 total: 0,
469 };
470
471 out.push(row);
472 }
473
474 self.prev_rows = next_prev;
475 #[cfg(target_os = "linux")]
479 {
480 let live: std::collections::HashSet<Pid> =
481 out.iter().filter(|r| !r.is_thread).map(|r| r.pid).collect();
482 self.prev_delay.retain(|pid, _| live.contains(pid));
483 }
484 out
485 }
486}
487
488fn task_stats(rows: &[Row]) -> crate::system::TaskStats {
495 let procs = rows.iter().filter(|r| !r.is_thread);
496 let mut stats = crate::system::TaskStats::default();
497 for row in procs {
498 stats.total += 1;
499 if row.state == b'R' {
500 stats.running += 1;
501 }
502 stats.threads += row.nlwp.max(1) as usize;
503 }
504 stats
505}
506
507impl crate::PlatformSampler for Sampler {
517 fn sample(&mut self) -> (SystemSnapshot, Vec<Row>) {
518 self.sample_inner()
519 }
520
521 fn cache_len(&self) -> usize {
522 self.cache.len()
523 }
524
525 fn backend_name() -> &'static str {
526 "linux (/proc)"
527 }
528
529 fn set_scan_options(&mut self, opts: crate::ScanOptions) {
530 self.show_threads = opts.threads;
531 self.read_smaps = opts.smaps;
532 self.read_gpu = opts.gpu;
533 #[cfg(target_os = "linux")]
534 {
535 self.read_delayacct = opts.delayacct;
536 if !opts.delayacct {
540 self.delayacct = None;
541 self.prev_delay.clear();
542 }
543 }
544 }
545
546 fn supports_threads() -> bool {
547 true
548 }
549}
550
551#[cfg(all(test, target_os = "linux"))]
555mod platform_trait_tests {
556 use crate::PlatformSampler;
557
558 #[test]
559 fn the_linux_sampler_satisfies_the_shared_trait() {
560 assert!(<crate::Sampler as PlatformSampler>::backend_name().contains("linux"));
561 let mut s = crate::Sampler::default();
562 let (_snap, rows) = PlatformSampler::sample(&mut s);
563 assert!(!rows.is_empty(), "/proc must yield processes");
564 assert_eq!(PlatformSampler::cache_len(&s), <crate::Sampler>::cache_len(&s));
565 }
566}