bench_io.rs 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. //! Bench I/O helpers: the output pump (tee the child's stdout/stderr to the
  2. //! console + the log file) and the newest-report lookup.
  3. use std::io::{Read, Write};
  4. use std::path::{Path, PathBuf};
  5. use std::sync::{Arc, Mutex};
  6. use std::time::SystemTime;
  7. use crate::ansi::strip_ansi_step;
  8. /// Tee one output pipe to the console and the log file.
  9. ///
  10. /// The console copy keeps the child's ANSI colors; the file copy is
  11. /// stripped (state machine below) so the saved .log stays readable in
  12. /// `sm121 report` and under grep/less.
  13. ///
  14. /// The read buffer must not be empty: `read()` on a zero-length slice
  15. /// returns 0 (interpreted as EOF), so an empty `Vec` would silently sever
  16. /// the pipe — the child then dies on its first stderr write with EPIPE
  17. /// (silent exit 120 in CPython).
  18. pub fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<std::fs::File>>) {
  19. let mut buf = vec![0u8; 8192];
  20. let mut carry = Vec::new(); // trailing partial escape across chunks
  21. let mut clean = Vec::new();
  22. loop {
  23. let n = match reader.read(&mut buf) {
  24. Ok(n) => n,
  25. Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
  26. Err(e) => panic!("reading bench output pipe: {e}"),
  27. };
  28. if n == 0 {
  29. break;
  30. }
  31. if to_console {
  32. std::io::stdout().write_all(&buf[..n]).ok();
  33. }
  34. carry.extend_from_slice(&buf[..n]);
  35. strip_ansi_step(&mut carry, &mut clean);
  36. if !clean.is_empty() {
  37. if let Ok(mut f) = file.lock() {
  38. f.write_all(&clean).ok();
  39. }
  40. clean.clear();
  41. }
  42. }
  43. }
  44. /// Newest `.md` under `scratch/runs/` (the tool's `runs/<date>/<run>.md`).
  45. pub fn latest_md(scratch: &Path) -> Option<PathBuf> {
  46. let runs = scratch.join("runs");
  47. let mut best: Option<(SystemTime, PathBuf)> = None;
  48. let mut stack = vec![runs];
  49. while let Some(dir) = stack.pop() {
  50. let entries = std::fs::read_dir(dir).ok()?;
  51. for e in entries.flatten() {
  52. let p = e.path();
  53. if p.is_dir() {
  54. stack.push(p);
  55. } else if p.extension().is_some_and(|x| x == "md") {
  56. let t = e
  57. .metadata()
  58. .and_then(|m| m.modified())
  59. .ok()
  60. .unwrap_or(SystemTime::UNIX_EPOCH);
  61. if best.as_ref().is_none_or(|(bt, _)| t > *bt) {
  62. best = Some((t, p));
  63. }
  64. }
  65. }
  66. }
  67. best.map(|(_, p)| p)
  68. }