atagen

roundabout

public
Star0
Watch0
6f5b84fad3ef5c6affdba81ee1864ee4423cd1c8
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: src/sys.rs

6f5b84fad3efatagen2026-09-27 03:59 UTC1
use std::{ffi::CString, os::unix::ffi::OsStrExt, path::Path};
6f5b84fad3efatagen2026-09-27 03:59 UTC2
6f5b84fad3efatagen2026-09-27 03:59 UTC3
use crate::{sched::FsId, units::Bytes};
6f5b84fad3efatagen2026-09-27 03:59 UTC4
6f5b84fad3efatagen2026-09-27 03:59 UTC5
pub fn cores() -> u32 {
6f5b84fad3efatagen2026-09-27 03:59 UTC6
    std::thread::available_parallelism().map_or(1, |n| n.get() as u32)
6f5b84fad3efatagen2026-09-27 03:59 UTC7
}
6f5b84fad3efatagen2026-09-27 03:59 UTC8
6f5b84fad3efatagen2026-09-27 03:59 UTC9
pub struct FsStat {
6f5b84fad3efatagen2026-09-27 03:59 UTC10
    pub id: FsId,
6f5b84fad3efatagen2026-09-27 03:59 UTC11
    pub free: Bytes,
6f5b84fad3efatagen2026-09-27 03:59 UTC12
    pub size: Bytes,
6f5b84fad3efatagen2026-09-27 03:59 UTC13
}
6f5b84fad3efatagen2026-09-27 03:59 UTC14
6f5b84fad3efatagen2026-09-27 03:59 UTC15
pub fn fs(path: &Path) -> Option<FsStat> {
6f5b84fad3efatagen2026-09-27 03:59 UTC16
    let c = CString::new(path.as_os_str().as_bytes()).ok()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC17
    let mut vfs = std::mem::MaybeUninit::<libc::statvfs>::uninit();
6f5b84fad3efatagen2026-09-27 03:59 UTC18
    let mut st = std::mem::MaybeUninit::<libc::stat>::uninit();
6f5b84fad3efatagen2026-09-27 03:59 UTC19
    unsafe {
6f5b84fad3efatagen2026-09-27 03:59 UTC20
        if libc::statvfs(c.as_ptr(), vfs.as_mut_ptr()) != 0
6f5b84fad3efatagen2026-09-27 03:59 UTC21
            || libc::stat(c.as_ptr(), st.as_mut_ptr()) != 0
6f5b84fad3efatagen2026-09-27 03:59 UTC22
        {
6f5b84fad3efatagen2026-09-27 03:59 UTC23
            return None;
6f5b84fad3efatagen2026-09-27 03:59 UTC24
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC25
        let vfs = vfs.assume_init();
6f5b84fad3efatagen2026-09-27 03:59 UTC26
        let st = st.assume_init();
6f5b84fad3efatagen2026-09-27 03:59 UTC27
        #[allow(clippy::unnecessary_cast)]
6f5b84fad3efatagen2026-09-27 03:59 UTC28
        let block = vfs.f_frsize as u64;
6f5b84fad3efatagen2026-09-27 03:59 UTC29
        #[allow(clippy::unnecessary_cast)]
6f5b84fad3efatagen2026-09-27 03:59 UTC30
        Some(FsStat {
6f5b84fad3efatagen2026-09-27 03:59 UTC31
            id: st.st_dev as u64,
6f5b84fad3efatagen2026-09-27 03:59 UTC32
            free: Bytes(vfs.f_bavail as u64 * block),
6f5b84fad3efatagen2026-09-27 03:59 UTC33
            size: Bytes(vfs.f_blocks as u64 * block),
6f5b84fad3efatagen2026-09-27 03:59 UTC34
        })
6f5b84fad3efatagen2026-09-27 03:59 UTC35
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC36
}
6f5b84fad3efatagen2026-09-27 03:59 UTC37
6f5b84fad3efatagen2026-09-27 03:59 UTC38
pub fn page_size() -> u64 {
6f5b84fad3efatagen2026-09-27 03:59 UTC39
    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 }
6f5b84fad3efatagen2026-09-27 03:59 UTC40
}
6f5b84fad3efatagen2026-09-27 03:59 UTC41
6f5b84fad3efatagen2026-09-27 03:59 UTC42
#[cfg(target_os = "linux")]
6f5b84fad3efatagen2026-09-27 03:59 UTC43
mod imp {
6f5b84fad3efatagen2026-09-27 03:59 UTC44
    use std::fs;
6f5b84fad3efatagen2026-09-27 03:59 UTC45
6f5b84fad3efatagen2026-09-27 03:59 UTC46
    use crate::units::Bytes;
6f5b84fad3efatagen2026-09-27 03:59 UTC47
6f5b84fad3efatagen2026-09-27 03:59 UTC48
    pub fn memory() -> (Bytes, Bytes) {
6f5b84fad3efatagen2026-09-27 03:59 UTC49
        let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
6f5b84fad3efatagen2026-09-27 03:59 UTC50
        let field = |name: &str| {
6f5b84fad3efatagen2026-09-27 03:59 UTC51
            text.lines()
6f5b84fad3efatagen2026-09-27 03:59 UTC52
                .find_map(|l| l.strip_prefix(name))
6f5b84fad3efatagen2026-09-27 03:59 UTC53
                .and_then(|rest| {
6f5b84fad3efatagen2026-09-27 03:59 UTC54
                    rest.trim()
6f5b84fad3efatagen2026-09-27 03:59 UTC55
                        .trim_end_matches("kB")
6f5b84fad3efatagen2026-09-27 03:59 UTC56
                        .trim()
6f5b84fad3efatagen2026-09-27 03:59 UTC57
                        .parse::<u64>()
6f5b84fad3efatagen2026-09-27 03:59 UTC58
                        .ok()
6f5b84fad3efatagen2026-09-27 03:59 UTC59
                })
6f5b84fad3efatagen2026-09-27 03:59 UTC60
                .map_or(Bytes::ZERO, |kib| Bytes(kib * 1024))
6f5b84fad3efatagen2026-09-27 03:59 UTC61
        };
6f5b84fad3efatagen2026-09-27 03:59 UTC62
        (field("MemTotal:"), field("MemAvailable:"))
6f5b84fad3efatagen2026-09-27 03:59 UTC63
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC64
6f5b84fad3efatagen2026-09-27 03:59 UTC65
    pub fn pressure() -> Option<f64> {
6f5b84fad3efatagen2026-09-27 03:59 UTC66
        let text = fs::read_to_string("/proc/pressure/memory").ok()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC67
        text.lines()
6f5b84fad3efatagen2026-09-27 03:59 UTC68
            .find(|l| l.starts_with("some"))?
6f5b84fad3efatagen2026-09-27 03:59 UTC69
            .split_whitespace()
6f5b84fad3efatagen2026-09-27 03:59 UTC70
            .find_map(|kv| kv.strip_prefix("avg10="))?
6f5b84fad3efatagen2026-09-27 03:59 UTC71
            .parse()
6f5b84fad3efatagen2026-09-27 03:59 UTC72
            .ok()
6f5b84fad3efatagen2026-09-27 03:59 UTC73
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC74
6f5b84fad3efatagen2026-09-27 03:59 UTC75
    pub fn group_rss(pgid: i32) -> Bytes {
6f5b84fad3efatagen2026-09-27 03:59 UTC76
        let page = super::page_size();
6f5b84fad3efatagen2026-09-27 03:59 UTC77
        let Ok(dir) = fs::read_dir("/proc") else {
6f5b84fad3efatagen2026-09-27 03:59 UTC78
            return Bytes::ZERO;
6f5b84fad3efatagen2026-09-27 03:59 UTC79
        };
6f5b84fad3efatagen2026-09-27 03:59 UTC80
        let mut total = 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC81
        for entry in dir.flatten() {
6f5b84fad3efatagen2026-09-27 03:59 UTC82
            let name = entry.file_name();
6f5b84fad3efatagen2026-09-27 03:59 UTC83
            if !name.as_encoded_bytes().iter().all(u8::is_ascii_digit) {
6f5b84fad3efatagen2026-09-27 03:59 UTC84
                continue;
6f5b84fad3efatagen2026-09-27 03:59 UTC85
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC86
            let Ok(stat) = fs::read_to_string(entry.path().join("stat")) else {
6f5b84fad3efatagen2026-09-27 03:59 UTC87
                continue;
6f5b84fad3efatagen2026-09-27 03:59 UTC88
            };
6f5b84fad3efatagen2026-09-27 03:59 UTC89
            // comm can hold spaces and parens, fields only make sense after the last ')'
6f5b84fad3efatagen2026-09-27 03:59 UTC90
            let Some(tail) = stat.rsplit_once(')').map(|(_, t)| t) else {
6f5b84fad3efatagen2026-09-27 03:59 UTC91
                continue;
6f5b84fad3efatagen2026-09-27 03:59 UTC92
            };
6f5b84fad3efatagen2026-09-27 03:59 UTC93
            let fields: Vec<&str> = tail.split_whitespace().collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC94
            let pgrp = fields.get(2).and_then(|f| f.parse::<i32>().ok());
6f5b84fad3efatagen2026-09-27 03:59 UTC95
            let rss = fields.get(21).and_then(|f| f.parse::<u64>().ok());
6f5b84fad3efatagen2026-09-27 03:59 UTC96
            if pgrp == Some(pgid) {
6f5b84fad3efatagen2026-09-27 03:59 UTC97
                total += rss.unwrap_or(0) * page;
6f5b84fad3efatagen2026-09-27 03:59 UTC98
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC99
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC100
        Bytes(total)
6f5b84fad3efatagen2026-09-27 03:59 UTC101
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC102
}
6f5b84fad3efatagen2026-09-27 03:59 UTC103
6f5b84fad3efatagen2026-09-27 03:59 UTC104
#[cfg(target_os = "macos")]
6f5b84fad3efatagen2026-09-27 03:59 UTC105
mod imp {
6f5b84fad3efatagen2026-09-27 03:59 UTC106
    use std::process::Command;
6f5b84fad3efatagen2026-09-27 03:59 UTC107
6f5b84fad3efatagen2026-09-27 03:59 UTC108
    use crate::units::Bytes;
6f5b84fad3efatagen2026-09-27 03:59 UTC109
6f5b84fad3efatagen2026-09-27 03:59 UTC110
    pub fn memory() -> (Bytes, Bytes) {
6f5b84fad3efatagen2026-09-27 03:59 UTC111
        let mut total: u64 = 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC112
        let mut len = std::mem::size_of::<u64>();
6f5b84fad3efatagen2026-09-27 03:59 UTC113
        unsafe {
6f5b84fad3efatagen2026-09-27 03:59 UTC114
            libc::sysctlbyname(
6f5b84fad3efatagen2026-09-27 03:59 UTC115
                c"hw.memsize".as_ptr(),
6f5b84fad3efatagen2026-09-27 03:59 UTC116
                (&raw mut total).cast(),
6f5b84fad3efatagen2026-09-27 03:59 UTC117
                &raw mut len,
6f5b84fad3efatagen2026-09-27 03:59 UTC118
                std::ptr::null_mut(),
6f5b84fad3efatagen2026-09-27 03:59 UTC119
                0,
6f5b84fad3efatagen2026-09-27 03:59 UTC120
            );
6f5b84fad3efatagen2026-09-27 03:59 UTC121
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC122
        // vm_stat over mach calls, libc's mach bindings are deprecated and this
6f5b84fad3efatagen2026-09-27 03:59 UTC123
        // runs once a second at most
6f5b84fad3efatagen2026-09-27 03:59 UTC124
        let out = Command::new("vm_stat")
6f5b84fad3efatagen2026-09-27 03:59 UTC125
            .output()
6f5b84fad3efatagen2026-09-27 03:59 UTC126
            .map(|o| o.stdout)
6f5b84fad3efatagen2026-09-27 03:59 UTC127
            .unwrap_or_default();
6f5b84fad3efatagen2026-09-27 03:59 UTC128
        let text = String::from_utf8_lossy(&out);
6f5b84fad3efatagen2026-09-27 03:59 UTC129
        let pages = |name: &str| {
6f5b84fad3efatagen2026-09-27 03:59 UTC130
            text.lines()
6f5b84fad3efatagen2026-09-27 03:59 UTC131
                .find_map(|l| l.strip_prefix(name))
6f5b84fad3efatagen2026-09-27 03:59 UTC132
                .and_then(|r| r.trim().trim_end_matches('.').parse::<u64>().ok())
6f5b84fad3efatagen2026-09-27 03:59 UTC133
                .unwrap_or(0)
6f5b84fad3efatagen2026-09-27 03:59 UTC134
        };
6f5b84fad3efatagen2026-09-27 03:59 UTC135
        let avail = (pages("Pages free:") + pages("Pages inactive:") + pages("Pages purgeable:"))
6f5b84fad3efatagen2026-09-27 03:59 UTC136
            * super::page_size();
6f5b84fad3efatagen2026-09-27 03:59 UTC137
        (Bytes(total), Bytes(avail))
6f5b84fad3efatagen2026-09-27 03:59 UTC138
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC139
6f5b84fad3efatagen2026-09-27 03:59 UTC140
    pub const fn pressure() -> Option<f64> {
6f5b84fad3efatagen2026-09-27 03:59 UTC141
        None
6f5b84fad3efatagen2026-09-27 03:59 UTC142
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC143
6f5b84fad3efatagen2026-09-27 03:59 UTC144
    pub fn group_rss(pgid: i32) -> Bytes {
6f5b84fad3efatagen2026-09-27 03:59 UTC145
        let out = Command::new("ps")
6f5b84fad3efatagen2026-09-27 03:59 UTC146
            .args(["-A", "-o", "pgid=,rss="])
6f5b84fad3efatagen2026-09-27 03:59 UTC147
            .output()
6f5b84fad3efatagen2026-09-27 03:59 UTC148
            .map(|o| o.stdout)
6f5b84fad3efatagen2026-09-27 03:59 UTC149
            .unwrap_or_default();
6f5b84fad3efatagen2026-09-27 03:59 UTC150
        let kib: u64 = String::from_utf8_lossy(&out)
6f5b84fad3efatagen2026-09-27 03:59 UTC151
            .lines()
6f5b84fad3efatagen2026-09-27 03:59 UTC152
            .filter_map(|l| {
6f5b84fad3efatagen2026-09-27 03:59 UTC153
                let mut f = l.split_whitespace();
6f5b84fad3efatagen2026-09-27 03:59 UTC154
                let g = f.next()?.parse::<i32>().ok()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC155
                let rss = f.next()?.parse::<u64>().ok()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC156
                (g == pgid).then_some(rss)
6f5b84fad3efatagen2026-09-27 03:59 UTC157
            })
6f5b84fad3efatagen2026-09-27 03:59 UTC158
            .sum();
6f5b84fad3efatagen2026-09-27 03:59 UTC159
        Bytes(kib * 1024)
6f5b84fad3efatagen2026-09-27 03:59 UTC160
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC161
}
6f5b84fad3efatagen2026-09-27 03:59 UTC162
6f5b84fad3efatagen2026-09-27 03:59 UTC163
pub use imp::{group_rss, memory, pressure};