Skip to main content

rigtop_core/linux/
mod.rs

1//! Linux-specific readers for `/sys` and `/proc`.
2//!
3//! These cover what `sysinfo` does not expose: hwmon sensors, RAPL energy
4//! counters, per-device disk I/O, btrfs, mdadm and ZFS state, failed
5//! systemd units, OOM kills, pending reboots, the containers processes run
6//! in, pressure stall information.
7//!
8//! Every reader takes its root directories from [`SysRoots`] instead of
9//! hard-coding `/sys` and `/proc`, so tests can point them at a fixture tree.
10
11pub mod battery;
12pub mod btrfs;
13pub mod containers;
14pub mod diskstats;
15pub mod drm;
16pub mod drm_clients;
17pub mod hwmon;
18pub mod mdstat;
19pub mod meminfo;
20pub mod mounts;
21pub mod net;
22pub mod oom;
23pub mod pressure;
24pub mod procstat;
25pub mod rapl;
26pub mod reboot;
27pub mod systemd;
28pub mod zfs;
29pub mod zpool;
30
31use std::fs;
32use std::path::{Path, PathBuf};
33
34/// Root directories of the kernel pseudo filesystems.
35#[derive(Debug, Clone)]
36pub struct SysRoots {
37    /// Normally `/sys`.
38    pub sys: PathBuf,
39    /// Normally `/proc`.
40    pub proc: PathBuf,
41}
42
43impl Default for SysRoots {
44    fn default() -> Self {
45        Self {
46            sys: PathBuf::from("/sys"),
47            proc: PathBuf::from("/proc"),
48        }
49    }
50}
51
52/// Reads a sysfs attribute and trims the trailing newline.
53///
54/// Returns `None` on any error: sysfs attributes may disappear at any time
55/// (hot-unplug) or be unreadable without root, and callers treat both as
56/// "not available".
57pub(crate) fn read_trimmed(path: &Path) -> Option<String> {
58    fs::read_to_string(path).ok().map(|s| s.trim().to_owned())
59}
60
61/// Reads a sysfs attribute holding a single unsigned integer.
62pub(crate) fn read_u64(path: &Path) -> Option<u64> {
63    read_trimmed(path)?.parse().ok()
64}
65
66/// Reads a sysfs attribute holding a single signed integer.
67pub(crate) fn read_i64(path: &Path) -> Option<i64> {
68    read_trimmed(path)?.parse().ok()
69}
70
71/// Returns the file names in `dir`, sorted, or an empty list if it cannot be read.
72pub(crate) fn sorted_entries(dir: &Path) -> Vec<String> {
73    let mut names: Vec<String> = match fs::read_dir(dir) {
74        Ok(entries) => entries
75            .filter_map(|e| e.ok())
76            .filter_map(|e| e.file_name().into_string().ok())
77            .collect(),
78        Err(_) => Vec::new(),
79    };
80    names.sort_by(|a, b| natural_cmp(a, b));
81    names
82}
83
84/// Compares strings so that embedded numbers sort numerically
85/// (`hwmon2` < `hwmon10`, `fan2` < `fan10`).
86pub(crate) fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
87    // Split into (non-digit prefix, trailing number). Kernel object names
88    // ("hwmon10", "nvme2", "md127") only carry a number at the end.
89    fn split(s: &str) -> (&str, Option<u64>) {
90        let digits_start = s
91            .char_indices()
92            .rev()
93            .take_while(|(_, c)| c.is_ascii_digit())
94            .last()
95            .map(|(i, _)| i)
96            .unwrap_or(s.len());
97        let (prefix, digits) = s.split_at(digits_start);
98        (prefix, digits.parse().ok())
99    }
100    let (pa, na) = split(a);
101    let (pb, nb) = split(b);
102    pa.cmp(pb).then(na.cmp(&nb)).then_with(|| a.cmp(b))
103}
104
105#[cfg(test)]
106pub(crate) mod testutil {
107    //! Helpers to build fake sysfs/procfs trees in a temporary directory.
108
109    use std::fs;
110    use std::path::Path;
111
112    /// Writes `content` to `root/rel`, creating parent directories.
113    pub fn write(root: &Path, rel: &str, content: &str) {
114        let path = root.join(rel);
115        fs::create_dir_all(path.parent().unwrap()).unwrap();
116        fs::write(path, content).unwrap();
117    }
118
119    /// Creates a symlink at `root/rel` pointing to `target` (absolute or relative).
120    pub fn symlink(root: &Path, rel: &str, target: &Path) {
121        let path = root.join(rel);
122        fs::create_dir_all(path.parent().unwrap()).unwrap();
123        std::os::unix::fs::symlink(target, path).unwrap();
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::natural_cmp;
130
131    #[test]
132    fn natural_order_sorts_numbers_numerically() {
133        let mut names = vec!["hwmon10", "hwmon2", "hwmon1", "fan"];
134        names.sort_by(|a, b| natural_cmp(a, b));
135        assert_eq!(names, vec!["fan", "hwmon1", "hwmon2", "hwmon10"]);
136    }
137}