| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | | use crate::home::Home; |
| 6 | | use flow_core::api::Facts; |
| 7 | | use std::time::Instant; |
| 8 | |
|
| 9 | |
|
| 10 | |
|
| 11 | | const REFERENCE_MS: f64 = 225.0; |
| 12 | |
|
| 13 | |
|
| 14 | 0× | pub fn now(home: &Home, speed: Option<u32>) -> Facts { |
| 15 | 0× | Facts { |
| 16 | 0× | free_disk_mib: free_disk_mib(&home.root), |
| 17 | 0× | load_x100: load_x100(), |
| 18 | 0× | speed, |
| 19 | 0× | speed_class: speed.map(Facts::class_of), |
| 20 | 0× | kernel: kernel(), |
| 21 | 0× | } |
| 22 | 0× | } |
| 23 | |
|
| 24 | |
|
| 25 | |
|
| 26 | 1× | pub fn kernel() -> Option<String> { |
| 27 | 1× | let mut u: libc::utsname = unsafe { std::mem::zeroed() }; |
| 28 | | |
| 29 | 1× | if unsafe { libc::uname(&mut u) } != 0 { |
| 30 | 0× | return None; |
| 31 | 1× | } |
| 32 | | |
| 33 | 1× | let release = unsafe { std::ffi::CStr::from_ptr(u.release.as_ptr()) }; |
| 34 | 1× | Some(release.to_string_lossy().into_owned()) |
| 35 | 1× | } |
| 36 | |
|
| 37 | |
|
| 38 | 1× | pub fn free_disk_mib(path: &std::path::Path) -> Option<u64> { |
| 39 | | use std::os::unix::ffi::OsStrExt; |
| 40 | 1× | let c = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?; |
| 41 | 1× | let mut s: libc::statvfs = unsafe { std::mem::zeroed() }; |
| 42 | | |
| 43 | 1× | if unsafe { libc::statvfs(c.as_ptr(), &mut s) } != 0 { |
| 44 | 0× | return None; |
| 45 | 1× | } |
| 46 | 1× | Some((s.f_bavail as u64).saturating_mul(s.f_frsize as u64) >> 20) |
| 47 | 1× | } |
| 48 | |
|
| 49 | |
|
| 50 | |
|
| 51 | 1× | pub fn allocated_mib(dir: &std::path::Path) -> Option<u64> { |
| 52 | | use std::os::unix::fs::MetadataExt; |
| 53 | 1× | let mut total: u64 = 0; |
| 54 | 1× | let mut stack = vec![dir.to_path_buf()]; |
| 55 | 2× | while let Some(d) = stack.pop() { |
| 56 | 2× | for e in std::fs::read_dir(&d).ok()?.flatten() { |
| 57 | 2× | let m = std::fs::symlink_metadata(e.path()).ok()?; |
| 58 | 2× | if m.is_dir() { |
| 59 | 0× | stack.push(e.path()); |
| 60 | 2× | } else { |
| 61 | 2× | total = total.saturating_add(m.blocks() * 512); |
| 62 | 2× | } |
| 63 | | } |
| 64 | | } |
| 65 | 1× | Some(total >> 20) |
| 66 | 1× | } |
| 67 | |
|
| 68 | |
|
| 69 | 16× | pub fn peak_rss_mib(pid: u32) -> Option<u64> { |
| 70 | 16× | let text = std::fs::read_to_string(format!("/proc/{pid}/status")).ok()?; |
| 71 | 353× | let kib: u64 = text.lines().find_map(|l| l.strip_prefix("VmHWM:"))?.trim().trim_end_matches("kB").trim().parse().ok()?; |
| 72 | 15× | Some(kib / 1024) |
| 73 | 16× | } |
| 74 | |
|
| 75 | |
|
| 76 | |
|
| 77 | 1× | pub fn load_x100() -> Option<u32> { |
| 78 | 1× | let mut i: libc::sysinfo = unsafe { std::mem::zeroed() }; |
| 79 | | |
| 80 | 1× | if unsafe { libc::sysinfo(&mut i) } != 0 { |
| 81 | 0× | return None; |
| 82 | 1× | } |
| 83 | | |
| 84 | 1× | Some(((i.loads[0] as f64) / 65536.0 * 100.0).round() as u32) |
| 85 | 1× | } |
| 86 | |
|
| 87 | |
|
| 88 | |
|
| 89 | |
|
| 90 | 0× | pub fn bench() -> (u32, u128) { |
| 91 | 0× | let start = Instant::now(); |
| 92 | 0× | let buf: Vec<u8> = (0..32u32 << 20).map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8).collect(); |
| 93 | 0× | let mut h = blake3::Hasher::new(); |
| 94 | 0× | for _ in 0..8 { |
| 95 | 0× | h.update(&buf); |
| 96 | 0× | } |
| 97 | 0× | std::hint::black_box(h.finalize()); |
| 98 | 0× | drop(buf); |
| 99 | 0× | let mut x: u64 = 0x9e37_79b9_7f4a_7c15; |
| 100 | 0× | let mut words: Vec<u64> = (0..4u64 << 20) |
| 101 | 0× | .map(|_| { |
| 102 | 0× | x ^= x << 13; |
| 103 | 0× | x ^= x >> 7; |
| 104 | 0× | x ^= x << 17; |
| 105 | 0× | x |
| 106 | 0× | }) |
| 107 | 0× | .collect(); |
| 108 | 0× | words.sort_unstable(); |
| 109 | 0× | std::hint::black_box(&words); |
| 110 | 0× | let ms = start.elapsed().as_millis().max(1); |
| 111 | 0× | ((REFERENCE_MS * 100.0 / ms as f64).round() as u32, ms) |
| 112 | 0× | } |
| 113 | |
|
| 114 | | #[cfg(test)] |
| 115 | | mod tests { |
| 116 | | use super::*; |
| 117 | |
|
| 118 | | #[test] |
| 119 | 1× | fn the_cheap_facts_are_read_and_classes_double() { |
| 120 | 1× | assert!(free_disk_mib(std::path::Path::new("/")).is_some()); |
| 121 | | |
| 122 | 1× | let proc_load: f64 = std::fs::read_to_string("/proc/loadavg").unwrap().split_whitespace().next().unwrap().parse().unwrap(); |
| 123 | 1× | let load = load_x100().unwrap() as f64 / 100.0; |
| 124 | 1× | assert!((load - proc_load).abs() < 1.0 + proc_load / 4.0, "sysinfo {load}, /proc {proc_load}"); |
| 125 | 1× | assert_eq!(kernel().unwrap(), std::fs::read_to_string("/proc/sys/kernel/osrelease").unwrap().trim()); |
| 126 | 1× | assert!(peak_rss_mib(std::process::id()).is_some_and(|m| m > 0)); |
| 127 | 1× | let dir = tempfile::tempdir().unwrap(); |
| 128 | 1× | let f = std::fs::File::create(dir.path().join("sparse")).unwrap(); |
| 129 | 1× | f.set_len(1 << 30).unwrap(); |
| 130 | 1× | std::fs::write(dir.path().join("full"), vec![7u8; 3 << 20]).unwrap(); |
| 131 | 1× | let mib = allocated_mib(dir.path()).unwrap(); |
| 132 | 1× | assert!((3..=4).contains(&mib), "the sparse gigabyte takes nothing, the written 3 MiB do: {mib}"); |
| 133 | 1× | assert_eq!(Facts::class_of(100), 3, "the reference machine"); |
| 134 | 1× | assert_eq!((Facts::class_of(50), Facts::class_of(200), Facts::class_of(500), Facts::class_of(10)), (2, 4, 5, 1)); |
| 135 | 1× | } |
| 136 | | } |