atagen

roundabout

public
Star0
Watch0
56a853ebf2549fd63ad8a481423b088d23dd0dc1
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git
src/sys.rsRust163 lines5.0 KB
1
use std::{ffi::CString, os::unix::ffi::OsStrExt, path::Path};
2
3
use crate::{sched::FsId, units::Bytes};
4
5
pub fn cores() -> u32 {
6
    std::thread::available_parallelism().map_or(1, |n| n.get() as u32)
7
}
8
9
pub struct FsStat {
10
    pub id: FsId,
11
    pub free: Bytes,
12
    pub size: Bytes,
13
}
14
15
pub fn fs(path: &Path) -> Option<FsStat> {
16
    let c = CString::new(path.as_os_str().as_bytes()).ok()?;
17
    let mut vfs = std::mem::MaybeUninit::<libc::statvfs>::uninit();
18
    let mut st = std::mem::MaybeUninit::<libc::stat>::uninit();
19
    unsafe {
20
        if libc::statvfs(c.as_ptr(), vfs.as_mut_ptr()) != 0
21
            || libc::stat(c.as_ptr(), st.as_mut_ptr()) != 0
22
        {
23
            return None;
24
        }
25
        let vfs = vfs.assume_init();
26
        let st = st.assume_init();
27
        #[allow(clippy::unnecessary_cast)]
28
        let block = vfs.f_frsize as u64;
29
        #[allow(clippy::unnecessary_cast)]
30
        Some(FsStat {
31
            id: st.st_dev as u64,
32
            free: Bytes(vfs.f_bavail as u64 * block),
33
            size: Bytes(vfs.f_blocks as u64 * block),
34
        })
35
    }
36
}
37
38
pub fn page_size() -> u64 {
39
    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 }
40
}
41
42
#[cfg(target_os = "linux")]
43
mod imp {
44
    use std::fs;
45
46
    use crate::units::Bytes;
47
48
    pub fn memory() -> (Bytes, Bytes) {
49
        let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
50
        let field = |name: &str| {
51
            text.lines()
52
                .find_map(|l| l.strip_prefix(name))
53
                .and_then(|rest| {
54
                    rest.trim()
55
                        .trim_end_matches("kB")
56
                        .trim()
57
                        .parse::<u64>()
58
                        .ok()
59
                })
60
                .map_or(Bytes::ZERO, |kib| Bytes(kib * 1024))
61
        };
62
        (field("MemTotal:"), field("MemAvailable:"))
63
    }
64
65
    pub fn pressure() -> Option<f64> {
66
        let text = fs::read_to_string("/proc/pressure/memory").ok()?;
67
        text.lines()
68
            .find(|l| l.starts_with("some"))?
69
            .split_whitespace()
70
            .find_map(|kv| kv.strip_prefix("avg10="))?
71
            .parse()
72
            .ok()
73
    }
74
75
    pub fn group_rss(pgid: i32) -> Bytes {
76
        let page = super::page_size();
77
        let Ok(dir) = fs::read_dir("/proc") else {
78
            return Bytes::ZERO;
79
        };
80
        let mut total = 0;
81
        for entry in dir.flatten() {
82
            let name = entry.file_name();
83
            if !name.as_encoded_bytes().iter().all(u8::is_ascii_digit) {
84
                continue;
85
            }
86
            let Ok(stat) = fs::read_to_string(entry.path().join("stat")) else {
87
                continue;
88
            };
89
            // comm can hold spaces and parens, fields only make sense after the last ')'
90
            let Some(tail) = stat.rsplit_once(')').map(|(_, t)| t) else {
91
                continue;
92
            };
93
            let fields: Vec<&str> = tail.split_whitespace().collect();
94
            let pgrp = fields.get(2).and_then(|f| f.parse::<i32>().ok());
95
            let rss = fields.get(21).and_then(|f| f.parse::<u64>().ok());
96
            if pgrp == Some(pgid) {
97
                total += rss.unwrap_or(0) * page;
98
            }
99
        }
100
        Bytes(total)
101
    }
102
}
103
104
#[cfg(target_os = "macos")]
105
mod imp {
106
    use std::process::Command;
107
108
    use crate::units::Bytes;
109
110
    pub fn memory() -> (Bytes, Bytes) {
111
        let mut total: u64 = 0;
112
        let mut len = std::mem::size_of::<u64>();
113
        unsafe {
114
            libc::sysctlbyname(
115
                c"hw.memsize".as_ptr(),
116
                (&raw mut total).cast(),
117
                &raw mut len,
118
                std::ptr::null_mut(),
119
                0,
120
            );
121
        }
122
        // vm_stat over mach calls, libc's mach bindings are deprecated and this
123
        // runs once a second at most
124
        let out = Command::new("vm_stat")
125
            .output()
126
            .map(|o| o.stdout)
127
            .unwrap_or_default();
128
        let text = String::from_utf8_lossy(&out);
129
        let pages = |name: &str| {
130
            text.lines()
131
                .find_map(|l| l.strip_prefix(name))
132
                .and_then(|r| r.trim().trim_end_matches('.').parse::<u64>().ok())
133
                .unwrap_or(0)
134
        };
135
        let avail = (pages("Pages free:") + pages("Pages inactive:") + pages("Pages purgeable:"))
136
            * super::page_size();
137
        (Bytes(total), Bytes(avail))
138
    }
139
140
    pub const fn pressure() -> Option<f64> {
141
        None
142
    }
143
144
    pub fn group_rss(pgid: i32) -> Bytes {
145
        let out = Command::new("ps")
146
            .args(["-A", "-o", "pgid=,rss="])
147
            .output()
148
            .map(|o| o.stdout)
149
            .unwrap_or_default();
150
        let kib: u64 = String::from_utf8_lossy(&out)
151
            .lines()
152
            .filter_map(|l| {
153
                let mut f = l.split_whitespace();
154
                let g = f.next()?.parse::<i32>().ok()?;
155
                let rss = f.next()?.parse::<u64>().ok()?;
156
                (g == pgid).then_some(rss)
157
            })
158
            .sum();
159
        Bytes(kib * 1024)
160
    }
161
}
162
163
pub use imp::{group_rss, memory, pressure};