OXYGENHUB
# ecce3719da80 oxygenflow / crates / flow / src / facts.rs
2.9 KiB · Rust · last changed Runners send their facts with every claim: free disk, load, speed 25 days ago source blame history raw
1
//! What a runner measures about itself (spec §11.1, docs/jetstream.md §6):
2
//! free disk, load and the kernel each claim, cheaply; and its speed, from
3
//! a short fixed benchmark run at start and again while idle.
4
5
use crate::home::Home;
6
use flow_core::api::Facts;
7
use std::time::Instant;
8
9
/// The benchmark's time on the reference machine (Earth), in ms: a runner
10
/// that takes as long scores 100.
11
const REFERENCE_MS: f64 = 225.0;
12
13
/// The cheap facts, with the last speed measured.
14
pub fn now(home: &Home, speed: Option<u32>) -> Facts {
15
    Facts {
16
        free_disk_mib: free_disk_mib(&home.root),
17
        load_x100: load_x100(),
18
        speed,
19
        speed_class: speed.map(Facts::class_of),
20
        kernel: std::fs::read_to_string("/proc/sys/kernel/osrelease").ok().map(|s| s.trim().to_string()),
21
    }
22
}
23
24
/// Free space for an unprivileged writer on the file system holding `path`, in MiB.
25
pub fn free_disk_mib(path: &std::path::Path) -> Option<u64> {
26
    use std::os::unix::ffi::OsStrExt;
27
    let c = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?;
28
    let mut s: libc::statvfs = unsafe { std::mem::zeroed() };
29
    // SAFETY: `c` is a valid NUL-terminated path and `s` a writable statvfs.
30
    if unsafe { libc::statvfs(c.as_ptr(), &mut s) } != 0 {
31
        return None;
32
    }
33
    Some((s.f_bavail as u64).saturating_mul(s.f_frsize as u64) >> 20)
34
}
35
36
/// The 1-minute load average from /proc/loadavg, times 100.
37
pub fn load_x100() -> Option<u32> {
38
    let text = std::fs::read_to_string("/proc/loadavg").ok()?;
39
    let first: f64 = text.split_whitespace().next()?.parse().ok()?;
40
    Some((first * 100.0).round() as u32)
41
}
42
43
/// The benchmark, one thread: BLAKE3 over 256 MiB (spec §11.1), and a sort
44
/// of 4 Mi pseudo-random words for the integer and memory side. Answers
45
/// the score, 100 on the reference machine, and the time it took.
46
pub fn bench() -> (u32, u128) {
47
    let start = Instant::now();
48
    let buf: Vec<u8> = (0..32u32 << 20).map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8).collect();
49
    let mut h = blake3::Hasher::new();
50
    for _ in 0..8 {
51
        h.update(&buf);
52
    }
53
    std::hint::black_box(h.finalize());
54
    drop(buf);
55
    let mut x: u64 = 0x9e37_79b9_7f4a_7c15;
56
    let mut words: Vec<u64> = (0..4u64 << 20)
57
        .map(|_| {
58
            x ^= x << 13;
59
            x ^= x >> 7;
60
            x ^= x << 17;
61
            x
62
        })
63
        .collect();
64
    words.sort_unstable();
65
    std::hint::black_box(&words);
66
    let ms = start.elapsed().as_millis().max(1);
67
    ((REFERENCE_MS * 100.0 / ms as f64).round() as u32, ms)
68
}
69
70
#[cfg(test)]
71
mod tests {
72
    use super::*;
73
74
    #[test]
75
    fn the_cheap_facts_are_read_and_classes_double() {
76
        assert!(free_disk_mib(std::path::Path::new("/")).is_some());
77
        assert!(load_x100().is_some());
78
        assert_eq!(Facts::class_of(100), 3, "the reference machine");
79
        assert_eq!((Facts::class_of(50), Facts::class_of(200), Facts::class_of(500), Facts::class_of(10)), (2, 4, 5, 1));
80
    }
81
}