use std::ffi::OsStr;
#[cfg(unix)]
use std::io::Write;
use std::sync::OnceLock;
use std::time::{Duration, Instant};
static ENABLED: OnceLock<bool> = OnceLock::new();
static ORIGIN: OnceLock<Instant> = OnceLock::new();
#[cfg(unix)]
static SINK: OnceLock<Option<std::fs::File>> = OnceLock::new();
pub(crate) fn enabled() -> bool {
*ENABLED.get_or_init(|| is_enabled(std::env::var_os("RETCH_TIMING").as_deref()))
}
fn is_enabled(value: Option<&OsStr>) -> bool {
matches!(value, Some(v) if !v.is_empty() && v != "0")
}
pub(crate) fn start() {
if enabled() {
ORIGIN.get_or_init(Instant::now);
#[cfg(unix)]
SINK.get_or_init(|| duplicate(std::io::stderr()));
}
}
#[cfg(unix)]
fn duplicate(fd: impl std::os::fd::AsFd) -> Option<std::fs::File> {
fd.as_fd()
.try_clone_to_owned()
.ok()
.map(std::fs::File::from)
}
fn emit(line: &str) {
#[cfg(unix)]
if let Some(Some(sink)) = SINK.get() {
let _ = (&*sink).write_all(format!("{line}\n").as_bytes());
return;
}
eprintln!("{line}");
}
pub(crate) fn timed<T>(label: &str, f: impl FnOnce() -> T) -> T {
if !enabled() {
return f();
}
let origin = *ORIGIN.get_or_init(Instant::now);
let t0 = Instant::now();
let out = f();
let took = t0.elapsed();
emit(&format_line(label, t0 - origin, took));
out
}
pub(crate) fn mark(label: &str) {
if enabled() {
let origin = *ORIGIN.get_or_init(Instant::now);
emit(&format_line(label, origin.elapsed(), Duration::ZERO));
}
}
fn format_line(label: &str, start: Duration, took: Duration) -> String {
format!(
"retch-timing {label:<16} start {:>9.2} ms took {:>9.2} ms",
start.as_secs_f64() * 1e3,
took.as_secs_f64() * 1e3
)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(target_os = "linux")]
#[test]
fn a_copy_taken_before_a_redirect_still_reaches_the_original_file() {
use std::io::{Read, Seek, Write};
use std::os::fd::AsRawFd;
let path = std::env::temp_dir().join(format!("retch-timing-{}", std::process::id()));
let original = std::fs::File::options()
.create(true)
.truncate(true)
.read(true)
.write(true)
.open(&path)
.expect("temp file");
let copy = duplicate(&original).expect("dup");
let devnull = std::fs::File::options()
.write(true)
.open("/dev/null")
.unwrap();
assert!(unsafe { libc::dup2(devnull.as_raw_fd(), original.as_raw_fd()) } >= 0);
(&original).write_all(b"lost\n").unwrap();
(©).write_all(b"kept\n").unwrap();
let mut reread = std::fs::File::open(&path).unwrap();
let mut text = String::new();
reread.rewind().unwrap();
reread.read_to_string(&mut text).unwrap();
let _ = std::fs::remove_file(&path);
assert_eq!(text, "kept\n");
}
#[test]
fn enabled_only_for_a_non_empty_non_zero_value() {
assert!(!is_enabled(None), "unset");
assert!(!is_enabled(Some(OsStr::new(""))), "empty");
assert!(!is_enabled(Some(OsStr::new("0"))), "explicitly off");
assert!(is_enabled(Some(OsStr::new("1"))));
assert!(is_enabled(Some(OsStr::new("yes"))));
}
#[test]
fn line_carries_label_offset_and_duration_in_ms() {
let line = format_line(
"audio",
Duration::from_micros(1_500),
Duration::from_micros(48_250),
);
assert_eq!(
line,
"retch-timing audio start 1.50 ms took 48.25 ms"
);
}
}