1use crate::util;
10use anyhow::{Context, Result, ensure};
11use serde_json::{Value, json};
12use std::{
13 ffi::CStr,
14 fs::File,
15 path::{Path, PathBuf},
16 time::Instant,
17};
18
19pub const READ_SAMPLE_BYTES: u64 = 2 * 1024 * 1024 * 1024;
21pub const READ_SAMPLE_THREADS: usize = 8;
23const READ_BLOCK: usize = 8 * 1024 * 1024;
24
25pub fn describe(model: &Path) -> Value {
29 let store = model.join("experts.bin");
30 let read = if store.is_file() {
31 read_rate(&store, READ_SAMPLE_BYTES, READ_SAMPLE_THREADS)
32 } else {
33 Err(anyhow::anyhow!(
34 "no packed expert store at <model>/experts.bin"
35 ))
36 };
37
38 json!({
39 "kernel": kernel(),
40 "os_version": os_version(),
41 "memory_bytes": physical_memory(),
42 "cpu_threads": std::thread::available_parallelism().map(|n| n.get()).ok(),
43 "profile": or_error(profile()),
44 "model_volume": or_error(volume(model)),
45 "store_read": or_error(read),
46 })
47}
48
49fn or_error(result: Result<Value>) -> Value {
50 result.unwrap_or_else(|e| json!({"error": e.to_string()}))
51}
52
53pub fn kernel() -> Option<String> {
55 let mut name: libc::utsname = unsafe { std::mem::zeroed() };
56
57 if unsafe { libc::uname(&mut name) } != 0 {
58 return None;
59 }
60
61 let field = |bytes: &[libc::c_char]| {
62 unsafe { CStr::from_ptr(bytes.as_ptr()) }
63 .to_string_lossy()
64 .into_owned()
65 };
66
67 Some(format!(
68 "{} {} {}",
69 field(&name.sysname),
70 field(&name.release),
71 field(&name.machine)
72 ))
73}
74
75pub fn physical_memory() -> Option<u64> {
77 let pages = unsafe { libc::sysconf(libc::_SC_PHYS_PAGES) };
78 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
79
80 (pages > 0 && page > 0).then(|| pages as u64 * page as u64)
81}
82
83fn os_version() -> Option<String> {
85 #[cfg(target_os = "macos")]
86 {
87 util::output(&["sw_vers", "-productVersion"]).ok()
88 }
89
90 #[cfg(not(target_os = "macos"))]
91 {
92 std::fs::read_to_string("/etc/os-release")
93 .ok()?
94 .lines()
95 .find_map(|line| line.strip_prefix("PRETTY_NAME="))
96 .map(|name| name.trim_matches('"').to_owned())
97 }
98}
99
100fn profile() -> Result<Value> {
103 #[cfg(target_os = "macos")]
104 {
105 let text = util::output(&[
106 "system_profiler",
107 "SPHardwareDataType",
108 "SPDisplaysDataType",
109 "SPNVMeDataType",
110 "-json",
111 ])?;
112
113 Ok(profile_fields(
114 &serde_json::from_str(&text).context("system_profiler JSON")?,
115 ))
116 }
117
118 #[cfg(not(target_os = "macos"))]
119 {
120 Ok(Value::Null)
121 }
122}
123
124pub fn profile_fields(value: &Value) -> Value {
126 let hardware = &value["SPHardwareDataType"][0];
127 let gpu = &value["SPDisplaysDataType"][0];
128 let drives: Vec<Value> = value["SPNVMeDataType"]
129 .as_array()
130 .into_iter()
131 .flatten()
132 .filter_map(|controller| controller["_items"].as_array())
133 .flatten()
134 .map(|drive| {
135 json!({
136 "model": drive["device_model"],
137 "size": drive["size"],
138 "size_bytes": drive["size_in_bytes"],
139 })
140 })
141 .collect();
142
143 json!({
144 "machine": hardware["machine_name"],
145 "model_identifier": hardware["machine_model"],
146 "chip": hardware["chip_type"],
147 "cpu_cores": hardware["number_processors"],
148 "memory": hardware["physical_memory"],
149 "gpu": gpu["sppci_model"],
150 "gpu_cores": gpu["sppci_cores"],
151 "metal_family": gpu["spdisplays_mtlgpufamilysupport"],
152 "nvme": drives,
153 })
154}
155
156pub fn volume(path: &Path) -> Result<Value> {
158 let c_path = std::ffi::CString::new(path.as_os_str().as_encoded_bytes())?;
159 let mut stat: libc::statvfs = unsafe { std::mem::zeroed() };
160
161 ensure!(
162 unsafe { libc::statvfs(c_path.as_ptr(), &mut stat) } == 0,
163 "statvfs failed: {}",
164 std::io::Error::last_os_error()
165 );
166
167 let fragment = stat.f_frsize as u64;
168
169 Ok(json!({
170 "total_bytes": stat.f_blocks as u64 * fragment,
171 "free_bytes": stat.f_bavail as u64 * fragment,
172 }))
173}
174
175pub fn read_rate(path: &Path, bytes: u64, threads: usize) -> Result<Value> {
178 ensure!(threads > 0, "read sample needs a reader");
179
180 let len = path.metadata()?.len();
181
182 ensure!(len > 0, "empty file");
183
184 let total = bytes.min(len);
185 let per_thread = (total / threads as u64).max(1);
186 let stride = len / threads as u64;
187 let started = Instant::now();
188 let done: u64 = std::thread::scope(|scope| {
189 let workers: Vec<_> = (0..threads)
190 .map(|i| {
191 let path: PathBuf = path.to_owned();
192 let offset = (stride * i as u64).min(len.saturating_sub(per_thread));
193
194 scope.spawn(move || read_uncached(&path, offset, per_thread))
195 })
196 .collect();
197
198 workers
199 .into_iter()
200 .map(|worker| worker.join().unwrap_or(Ok(0)))
201 .sum::<Result<u64>>()
202 })?;
203 let seconds = started.elapsed().as_secs_f64();
204
205 ensure!(done > 0 && seconds > 0.0, "read sample produced no data");
206
207 Ok(json!({
208 "file": path.file_name().map(|f| f.to_string_lossy().into_owned()),
209 "bytes": done,
210 "seconds": seconds,
211 "gbps": done as f64 / seconds / 1e9,
212 "threads": threads,
213 "method": "uncached 8 MiB reads at spread offsets; a warm-machine sample, not a cold-disk benchmark",
214 }))
215}
216
217fn read_uncached(path: &Path, offset: u64, bytes: u64) -> Result<u64> {
218 use std::os::unix::{fs::FileExt, io::AsRawFd};
219
220 let file = File::open(path)?;
221
222 bypass_cache(file.as_raw_fd(), offset, bytes);
223
224 let mut buffer = vec![0u8; READ_BLOCK];
225 let mut done = 0u64;
226
227 while done < bytes {
228 let want = buffer.len().min((bytes - done) as usize);
229 let read = file.read_at(&mut buffer[..want], offset + done)?;
230
231 if read == 0 {
232 break;
233 }
234
235 done += read as u64;
236 }
237
238 Ok(done)
239}
240
241#[cfg(target_os = "macos")]
243fn bypass_cache(fd: libc::c_int, _offset: u64, _bytes: u64) {
244 unsafe {
245 libc::fcntl(fd, libc::F_NOCACHE, 1);
246 }
247}
248
249#[cfg(target_os = "linux")]
250fn bypass_cache(fd: libc::c_int, offset: u64, bytes: u64) {
251 unsafe {
252 libc::posix_fadvise(
253 fd,
254 offset as libc::off_t,
255 bytes as libc::off_t,
256 libc::POSIX_FADV_DONTNEED,
257 );
258 }
259}
260
261#[cfg(not(any(target_os = "macos", target_os = "linux")))]
262fn bypass_cache(_fd: libc::c_int, _offset: u64, _bytes: u64) {}
263
264pub fn archive(out: &Path) -> Result<PathBuf> {
268 use std::io::Write;
269
270 let parent = out.parent().context("results directory parent")?;
271 let name = out
272 .file_name()
273 .context("results directory name")?
274 .to_string_lossy()
275 .into_owned();
276 let zip = parent.join(format!("{name}.zip"));
277 let mut writer = zip::ZipWriter::new(File::create(&zip)?);
278 let options = zip::write::SimpleFileOptions::default()
279 .compression_method(zip::CompressionMethod::Deflated)
280 .large_file(true);
281
282 for path in util::files(out)? {
283 let relative = path.strip_prefix(out)?;
284
285 writer.start_file(format!("{name}/{}", relative.to_string_lossy()), options)?;
286 writer.write_all(&std::fs::read(&path)?)?;
287 }
288
289 writer.finish()?;
290
291 Ok(zip)
292}