| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 |
- //! Bench I/O helpers: the output pump (tee the child's stdout/stderr to the
- //! console + the log file) and the newest-report lookup.
- use std::io::{Read, Write};
- use std::path::{Path, PathBuf};
- use std::sync::{Arc, Mutex};
- use std::time::SystemTime;
- use crate::ansi::strip_ansi_step;
- /// Tee one output pipe to the console and the log file.
- ///
- /// The console copy keeps the child's ANSI colors; the file copy is
- /// stripped (state machine below) so the saved .log stays readable in
- /// `sm121 report` and under grep/less.
- ///
- /// The read buffer must not be empty: `read()` on a zero-length slice
- /// returns 0 (interpreted as EOF), so an empty `Vec` would silently sever
- /// the pipe — the child then dies on its first stderr write with EPIPE
- /// (silent exit 120 in CPython).
- pub fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<std::fs::File>>) {
- let mut buf = vec![0u8; 8192];
- let mut carry = Vec::new(); // trailing partial escape across chunks
- let mut clean = Vec::new();
- loop {
- let n = match reader.read(&mut buf) {
- Ok(n) => n,
- Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
- Err(e) => panic!("reading bench output pipe: {e}"),
- };
- if n == 0 {
- break;
- }
- if to_console {
- std::io::stdout().write_all(&buf[..n]).ok();
- }
- carry.extend_from_slice(&buf[..n]);
- strip_ansi_step(&mut carry, &mut clean);
- if !clean.is_empty() {
- if let Ok(mut f) = file.lock() {
- f.write_all(&clean).ok();
- }
- clean.clear();
- }
- }
- }
- /// Newest `.md` under `scratch/runs/` (the tool's `runs/<date>/<run>.md`).
- pub fn latest_md(scratch: &Path) -> Option<PathBuf> {
- let runs = scratch.join("runs");
- let mut best: Option<(SystemTime, PathBuf)> = None;
- let mut stack = vec![runs];
- while let Some(dir) = stack.pop() {
- let entries = std::fs::read_dir(dir).ok()?;
- for e in entries.flatten() {
- let p = e.path();
- if p.is_dir() {
- stack.push(p);
- } else if p.extension().is_some_and(|x| x == "md") {
- let t = e
- .metadata()
- .and_then(|m| m.modified())
- .ok()
- .unwrap_or(SystemTime::UNIX_EPOCH);
- if best.as_ref().is_none_or(|(bt, _)| t > *bt) {
- best = Some((t, p));
- }
- }
- }
- }
- best.map(|(_, p)| p)
- }
|