A runner reads its load and kernel without /proc (refs andymccall/oxygenhub-android#8) 337afb8f6043
sysinfo and uname give the same numbers; an Android app may not read /proc/loadavg or /proc/sys/kernel/osrelease, so the phone runner was refused, and the refusal logged, at every claim.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key c2bbadac80df
- When
- · 2026-10-25 16:55 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
337afb8f604306f39990537f04b0259892164b1bf205bb374ff7b6d110b4e267- Depends on
- 4f5e2af75816 62829d15ef09 8f211e20bea5 c3db750ed96e ea56c56eaf90
- Depended on by
- f4dfd742b30d 3554afefb6aa 06a4c472b385
- In bookmarks
- main
- In tags
- v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
- Edits
- 9 atoms, 0 tree operations, 3 files
Browse the tree at this change
modified CHANGELOG.md +1 −0
| … | 3 unchanged lines |
||
| 4 | 4 | ||
| 5 | 5 | ## Unreleased |
|
| 6 | 6 | ||
| 7 | + | - **A runner reads its load and kernel without /proc:** `sysinfo` and `uname`, the same numbers; an Android app may not read `/proc/loadavg` or `/proc/sys/kernel/osrelease`, so the phone runner was refused, and logged, at every claim (oxygenhub-android#8). |
|
| 7 | 8 | - **Built on oxygen's main with #140 and #141:** the runner's store puts objects in place where hard links are refused (Android, so the phone runner can clone), and the `deps/oxygen` pin conflict two replacements in one change left on main is settled with `oxy resolve deps/oxygen`. |
|
| 8 | 9 | - **The WASM tier (#75):** `runtime "wasm"` runs WASI preview 1 modules with wasmtime 49 on any runner (`flow tiers` offers `wasm` everywhere): no rootfs, no network, the job's files at `/src`, its mounts at their targets, the memory limit enforced by wasmtime. `exec` names a module or a glob of them (`target/wasm32-wasip1/debug/deps/*.wasm` runs each test binary in turn); the receipt names wasmtime's version and every module's hash. The tier phones and browsers will run (docs/handover.md). Secrets and identities are not offered in it yet. |
|
| 9 | 10 | - **cargo-nextest in the Rust image (#81):** for JUnit test reports beside `cargo llvm-cov`'s coverage. |
|
| … | 87 unchanged lines |
modified crates/flow/src/facts.rs +28 −6
| … | 16 unchanged lines |
||
| 17 | 17 | load_x100: load_x100(), |
|
| 18 | 18 | speed, |
|
| 19 | 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()), |
|
| 20 | + | kernel: kernel(), |
|
| 21 | 21 | } |
|
| 22 | 22 | } |
|
| 23 | 23 | ||
| 24 | + | /// The kernel's release, as uname says. Not read from /proc: an Android |
|
| 25 | + | /// app may not read it there, and is refused (and logged) every claim. |
|
| 26 | + | pub fn kernel() -> Option<String> { |
|
| 27 | + | let mut u: libc::utsname = unsafe { std::mem::zeroed() }; |
|
| 28 | + | // SAFETY: `u` is a writable utsname. |
|
| 29 | + | if unsafe { libc::uname(&mut u) } != 0 { |
|
| 30 | + | return None; |
|
| 31 | + | } |
|
| 32 | + | // SAFETY: uname writes NUL-terminated strings. |
|
| 33 | + | let release = unsafe { std::ffi::CStr::from_ptr(u.release.as_ptr()) }; |
|
| 34 | + | Some(release.to_string_lossy().into_owned()) |
|
| 35 | + | } |
|
| 36 | + | ||
| 24 | 37 | /// Free space for an unprivileged writer on the file system holding `path`, in MiB. |
|
| 25 | 38 | pub fn free_disk_mib(path: &std::path::Path) -> Option<u64> { |
|
| 26 | 39 | use std::os::unix::ffi::OsStrExt; |
|
| … | 32 unchanged lines |
||
| 59 | 72 | Some(kib / 1024) |
|
| 60 | 73 | } |
|
| 61 | 74 | ||
| 62 | - | /// The 1-minute load average from /proc/loadavg, times 100. |
|
| 75 | + | /// The 1-minute load average, times 100, from sysinfo (the kernel's own |
|
| 76 | + | /// numbers behind /proc/loadavg, which an Android app may not read). |
|
| 63 | 77 | pub fn load_x100() -> Option<u32> { |
|
| 64 | - | let text = std::fs::read_to_string("/proc/loadavg").ok()?; |
|
| 65 | - | let first: f64 = text.split_whitespace().next()?.parse().ok()?; |
|
| 66 | - | Some((first * 100.0).round() as u32) |
|
| 78 | + | let mut i: libc::sysinfo = unsafe { std::mem::zeroed() }; |
|
| 79 | + | // SAFETY: `i` is a writable sysinfo. |
|
| 80 | + | if unsafe { libc::sysinfo(&mut i) } != 0 { |
|
| 81 | + | return None; |
|
| 82 | + | } |
|
| 83 | + | // Fixed point with 16 fraction bits (SI_LOAD_SHIFT). |
|
| 84 | + | Some(((i.loads[0] as f64) / 65536.0 * 100.0).round() as u32) |
|
| 67 | 85 | } |
|
| 68 | 86 | ||
| 69 | 87 | /// The benchmark, one thread: BLAKE3 over 256 MiB (spec §11.1), and a sort |
|
| … | 30 unchanged lines |
||
| 100 | 118 | #[test] |
|
| 101 | 119 | fn the_cheap_facts_are_read_and_classes_double() { |
|
| 102 | 120 | assert!(free_disk_mib(std::path::Path::new("/")).is_some()); |
|
| 103 | - | assert!(load_x100().is_some()); |
|
| 121 | + | // The same numbers /proc gives, read without it (an Android app may not). |
|
| 122 | + | let proc_load: f64 = std::fs::read_to_string("/proc/loadavg").unwrap().split_whitespace().next().unwrap().parse().unwrap(); |
|
| 123 | + | let load = load_x100().unwrap() as f64 / 100.0; |
|
| 124 | + | assert!((load - proc_load).abs() < 1.0 + proc_load / 4.0, "sysinfo {load}, /proc {proc_load}"); |
|
| 125 | + | assert_eq!(kernel().unwrap(), std::fs::read_to_string("/proc/sys/kernel/osrelease").unwrap().trim()); |
|
| 104 | 126 | assert!(peak_rss_mib(std::process::id()).is_some_and(|m| m > 0)); |
|
| 105 | 127 | let dir = tempfile::tempdir().unwrap(); |
|
| 106 | 128 | let f = std::fs::File::create(dir.path().join("sparse")).unwrap(); |
|
| … | 8 unchanged lines |
modified crates/flow/src/syscall.rs +1 −1
| … | 59 unchanged lines |
||
| 60 | 60 | } |
|
| 61 | 61 | ||
| 62 | 62 | fn kernel_release() -> String { |
|
| 63 | - | std::fs::read_to_string("/proc/sys/kernel/osrelease").map(|s| s.trim().to_string()).unwrap_or_else(|_| "unknown".into()) |
|
| 63 | + | crate::facts::kernel().unwrap_or_else(|| "unknown".into()) |
|
| 64 | 64 | } |
|
| 65 | 65 | ||
| 66 | 66 | /// Whether this machine can run the tier now: a cgroup this runner may |
|
| … | 799 unchanged lines |