| 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 | | #[cfg(unix)] |
| 27 | 1× | pub fn kernel() -> Option<String> { |
| 28 | 1× | let mut u: libc::utsname = unsafe { std::mem::zeroed() }; |
| 29 | | |
| 30 | 1× | if unsafe { libc::uname(&mut u) } != 0 { |
| 31 | 0× | return None; |
| 32 | 1× | } |
| 33 | | |
| 34 | 1× | let release = unsafe { std::ffi::CStr::from_ptr(u.release.as_ptr()) }; |
| 35 | 1× | Some(release.to_string_lossy().into_owned()) |
| 36 | 1× | } |
| 37 | |
|
| 38 | |
|
| 39 | |
|
| 40 | | #[cfg(windows)] |
| 41 | | pub fn kernel() -> Option<String> { |
| 42 | | use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW; |
| 43 | | |
| 44 | | let mut v: OSVERSIONINFOW = unsafe { std::mem::zeroed() }; |
| 45 | | v.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOW>() as u32; |
| 46 | | |
| 47 | | let status = unsafe { windows_sys::Wdk::System::SystemServices::RtlGetVersion(&mut v) }; |
| 48 | | (status == 0).then(|| format!("Windows {}.{}.{}", v.dwMajorVersion, v.dwMinorVersion, v.dwBuildNumber)) |
| 49 | | } |
| 50 | |
|
| 51 | |
|
| 52 | |
|
| 53 | | #[cfg(unix)] |
| 54 | 1× | pub fn free_disk_mib(path: &std::path::Path) -> Option<u64> { |
| 55 | | use std::os::unix::ffi::OsStrExt; |
| 56 | 1× | let c = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?; |
| 57 | 1× | let mut s: libc::statvfs = unsafe { std::mem::zeroed() }; |
| 58 | | |
| 59 | 1× | if unsafe { libc::statvfs(c.as_ptr(), &mut s) } != 0 { |
| 60 | 0× | return None; |
| 61 | 1× | } |
| 62 | 1× | Some((s.f_bavail as u64).saturating_mul(s.f_frsize as u64) >> 20) |
| 63 | 1× | } |
| 64 | |
|
| 65 | | #[cfg(windows)] |
| 66 | | pub fn free_disk_mib(path: &std::path::Path) -> Option<u64> { |
| 67 | | use std::os::windows::ffi::OsStrExt; |
| 68 | | let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect(); |
| 69 | | let mut free: u64 = 0; |
| 70 | | |
| 71 | | |
| 72 | | let ok = unsafe { |
| 73 | | windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExW(wide.as_ptr(), &mut free, std::ptr::null_mut(), std::ptr::null_mut()) |
| 74 | | }; |
| 75 | | (ok != 0).then_some(free >> 20) |
| 76 | | } |
| 77 | |
|
| 78 | |
|
| 79 | |
|
| 80 | 1× | pub fn allocated_mib(dir: &std::path::Path) -> Option<u64> { |
| 81 | 1× | let mut total: u64 = 0; |
| 82 | 1× | let mut stack = vec![dir.to_path_buf()]; |
| 83 | 2× | while let Some(d) = stack.pop() { |
| 84 | 2× | for e in std::fs::read_dir(&d).ok()?.flatten() { |
| 85 | 2× | let m = std::fs::symlink_metadata(e.path()).ok()?; |
| 86 | 2× | if m.is_dir() { |
| 87 | 0× | stack.push(e.path()); |
| 88 | 2× | } else { |
| 89 | 2× | total = total.saturating_add(crate::portable::disk_bytes(&m)); |
| 90 | 2× | } |
| 91 | | } |
| 92 | | } |
| 93 | 1× | Some(total >> 20) |
| 94 | 1× | } |
| 95 | |
|
| 96 | |
|
| 97 | 16× | pub fn peak_rss_mib(pid: u32) -> Option<u64> { |
| 98 | 16× | let text = std::fs::read_to_string(format!("/proc/{pid}/status")).ok()?; |
| 99 | 421× | let kib: u64 = text.lines().find_map(|l| l.strip_prefix("VmHWM:"))?.trim().trim_end_matches("kB").trim().parse().ok()?; |
| 100 | 11× | Some(kib / 1024) |
| 101 | 16× | } |
| 102 | |
|
| 103 | |
|
| 104 | |
|
| 105 | |
|
| 106 | | #[cfg(not(unix))] |
| 107 | | pub fn load_x100() -> Option<u32> { |
| 108 | | None |
| 109 | | } |
| 110 | |
|
| 111 | | #[cfg(unix)] |
| 112 | 1× | pub fn load_x100() -> Option<u32> { |
| 113 | 1× | let mut i: libc::sysinfo = unsafe { std::mem::zeroed() }; |
| 114 | | |
| 115 | 1× | if unsafe { libc::sysinfo(&mut i) } != 0 { |
| 116 | 0× | return None; |
| 117 | 1× | } |
| 118 | | |
| 119 | 1× | Some(((i.loads[0] as f64) / 65536.0 * 100.0).round() as u32) |
| 120 | 1× | } |
| 121 | |
|
| 122 | |
|
| 123 | |
|
| 124 | |
|
| 125 | 0× | pub fn bench() -> (u32, u128) { |
| 126 | 0× | let start = Instant::now(); |
| 127 | 0× | let buf: Vec<u8> = (0..32u32 << 20).map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8).collect(); |
| 128 | 0× | let mut h = blake3::Hasher::new(); |
| 129 | 0× | for _ in 0..8 { |
| 130 | 0× | h.update(&buf); |
| 131 | 0× | } |
| 132 | 0× | std::hint::black_box(h.finalize()); |
| 133 | 0× | drop(buf); |
| 134 | 0× | let mut x: u64 = 0x9e37_79b9_7f4a_7c15; |
| 135 | 0× | let mut words: Vec<u64> = (0..4u64 << 20) |
| 136 | 0× | .map(|_| { |
| 137 | 0× | x ^= x << 13; |
| 138 | 0× | x ^= x >> 7; |
| 139 | 0× | x ^= x << 17; |
| 140 | 0× | x |
| 141 | 0× | }) |
| 142 | 0× | .collect(); |
| 143 | 0× | words.sort_unstable(); |
| 144 | 0× | std::hint::black_box(&words); |
| 145 | 0× | let ms = start.elapsed().as_millis().max(1); |
| 146 | 0× | ((REFERENCE_MS * 100.0 / ms as f64).round() as u32, ms) |
| 147 | 0× | } |
| 148 | |
|
| 149 | | #[cfg(test)] |
| 150 | | mod tests { |
| 151 | | use super::*; |
| 152 | |
|
| 153 | | #[test] |
| 154 | 1× | fn the_cheap_facts_are_read_and_classes_double() { |
| 155 | 1× | assert!(free_disk_mib(std::path::Path::new("/")).is_some()); |
| 156 | | |
| 157 | 1× | let proc_load: f64 = std::fs::read_to_string("/proc/loadavg").unwrap().split_whitespace().next().unwrap().parse().unwrap(); |
| 158 | 1× | let load = load_x100().unwrap() as f64 / 100.0; |
| 159 | 1× | assert!((load - proc_load).abs() < 1.0 + proc_load / 4.0, "sysinfo {load}, /proc {proc_load}"); |
| 160 | 1× | assert_eq!(kernel().unwrap(), std::fs::read_to_string("/proc/sys/kernel/osrelease").unwrap().trim()); |
| 161 | 1× | assert!(peak_rss_mib(std::process::id()).is_some_and(|m| m > 0)); |
| 162 | 1× | let dir = tempfile::tempdir().unwrap(); |
| 163 | 1× | let f = std::fs::File::create(dir.path().join("sparse")).unwrap(); |
| 164 | 1× | f.set_len(1 << 30).unwrap(); |
| 165 | 1× | std::fs::write(dir.path().join("full"), vec![7u8; 3 << 20]).unwrap(); |
| 166 | 1× | let mib = allocated_mib(dir.path()).unwrap(); |
| 167 | 1× | assert!((3..=4).contains(&mib), "the sparse gigabyte takes nothing, the written 3 MiB do: {mib}"); |
| 168 | 1× | assert_eq!(Facts::class_of(100), 3, "the reference machine"); |
| 169 | 1× | assert_eq!((Facts::class_of(50), Facts::class_of(200), Facts::class_of(500), Facts::class_of(10)), (2, 4, 5, 1)); |
| 170 | 1× | } |
| 171 | | } |