Skip to main content

wowlab_common/sys/
linux.rs

1/// Read the current process RSS in megabytes.
2#[must_use]
3pub fn read_memory_mb() -> f64 {
4    // SAFETY: sysconf with valid constant always succeeds.
5    let page_size = wowlab_types::numeric::i64_to_f64(unsafe { libc::sysconf(libc::_SC_PAGESIZE) });
6
7    wowlab_fs::file::read_text(wowlab_fs::path::Path::new("/proc/self/statm"))
8        .ok()
9        .and_then(|s| {
10            s.split_whitespace()
11                .nth(1)
12                .and_then(|p| p.parse::<u64>().ok())
13        })
14        .map_or(0.0, |pages| {
15            wowlab_types::numeric::u64_to_f64(pages) * page_size / super::BYTES_PER_MB
16        })
17}
18
19/// Read the system load average `[1m, 5m, 15m]`.
20#[must_use]
21pub fn read_load_average() -> [f64; super::LOAD_AVG_COUNT] {
22    const LOAD_AVG_COUNT_I32: i32 = 3;
23    let mut avg = [0.0_f64; super::LOAD_AVG_COUNT];
24    // SAFETY: avg array has exactly LOAD_AVG_COUNT elements matching the count argument.
25    let ret = unsafe { libc::getloadavg(avg.as_mut_ptr(), LOAD_AVG_COUNT_I32) };
26
27    if ret == LOAD_AVG_COUNT_I32 {
28        avg
29    } else {
30        [0.0; super::LOAD_AVG_COUNT]
31    }
32}
33
34/// Read a CPU tick snapshot from `/proc/stat`.
35#[must_use]
36pub fn read_cpu_ticks() -> Option<super::CpuTicks> {
37    let stat = wowlab_fs::file::read_text(wowlab_fs::path::Path::new("/proc/stat")).ok()?;
38    let cpu = stat.lines().find(|l| l.starts_with("cpu "))?;
39    let mut parts = cpu
40        .split_whitespace()
41        .skip(1)
42        .filter_map(|v| v.parse::<u64>().ok());
43    let user = parts.next()?;
44    let nice = parts.next()?;
45    let system = parts.next()?;
46    let idle = parts.next()?;
47    let total = user + nice + system + idle;
48
49    Some(super::CpuTicks::new(total, idle))
50}
51
52/// Read total and available OS memory in megabytes.
53#[must_use]
54pub fn read_os_memory_mb() -> (f64, f64) {
55    let Ok(meminfo) = wowlab_fs::file::read_text(wowlab_fs::path::Path::new("/proc/meminfo"))
56    else {
57        return (0.0, 0.0);
58    };
59    let mut total = 0.0;
60    let mut available = 0.0;
61
62    for line in meminfo.lines() {
63        if line.starts_with("MemTotal:") {
64            total = line
65                .split_whitespace()
66                .nth(1)
67                .and_then(|v| v.parse::<f64>().ok())
68                .unwrap_or(0.0)
69                / wowlab_types::constants::BYTES_PER_KB;
70        } else if line.starts_with("MemAvailable:") {
71            available = line
72                .split_whitespace()
73                .nth(1)
74                .and_then(|v| v.parse::<f64>().ok())
75                .unwrap_or(0.0)
76                / wowlab_types::constants::BYTES_PER_KB;
77        }
78    }
79
80    (total, available)
81}
82
83/// CPU model string from `/proc/cpuinfo`.
84#[must_use]
85pub fn cpu_model() -> String {
86    let Ok(cpuinfo) = wowlab_fs::file::read_text(wowlab_fs::path::Path::new("/proc/cpuinfo"))
87    else {
88        return String::new();
89    };
90
91    for line in cpuinfo.lines() {
92        if line.starts_with("model name") {
93            if let Some(val) = line.split(':').nth(1) {
94                // #t(rust_alloc_in_loop) returns immediately, only runs once
95                return val.trim().to_string();
96            }
97        }
98    }
99
100    String::new()
101}