Distinguish console writes from other writes

This commit is contained in:
David Peter committed 2026-10-02 11:12:03 +02:00
1 parent 47339b0862
commit 73e79ebd07
6 files changed
+49 -35

No files matched your search

+13 -10
View File
@@ -13,6 +13,7 @@ use crate::options::{
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
};
use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
use crate::output::console_writeln;
use crate::output::format::{format_duration, format_duration_unit};
use crate::output::progress_bar::get_progress_bar;
use crate::output::warnings::{OutlierWarningOptions, Warnings};
@@ -141,7 +142,7 @@ impl<'a> Benchmark<'a> {
/// Run the benchmark for a single command
pub fn run(&self) -> Result<BenchmarkResult> {
if self.options.output_style != OutputStyleOption::Disabled {
writeln!(
console_writeln!(
io::stdout(),
"{}{}: {}",
"Benchmark ".bold(),
@@ -360,9 +361,10 @@ impl<'a> Benchmark<'a> {
let system_str = format_duration(system_mean, Some(time_unit));
if self.options.output_style != OutputStyleOption::Disabled {
let mut stdout = io::stdout().lock();
if times_real.len() == 1 {
writeln!(
io::stdout(),
console_writeln!(
stdout,
" Time ({} ≡): {:>8} {:>8} [User: {}, System: {}]",
"abs".green().bold(),
mean_str.green().bold(),
@@ -373,8 +375,8 @@ impl<'a> Benchmark<'a> {
} else {
let stddev_str = format_duration(t_stddev.unwrap(), Some(time_unit));
writeln!(
io::stdout(),
console_writeln!(
stdout,
" Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]",
"mean".green().bold(),
"σ".green(),
@@ -384,8 +386,8 @@ impl<'a> Benchmark<'a> {
system_str.blue()
)?;
writeln!(
io::stdout(),
console_writeln!(
stdout,
" Range ({} … {}): {:>8} … {:>8} {}",
"min".cyan(),
"max".purple(),
@@ -435,15 +437,16 @@ impl<'a> Benchmark<'a> {
}
if !warnings.is_empty() {
writeln!(io::stderr(), " ")?;
let mut stderr = io::stderr().lock();
console_writeln!(stderr, " ")?;
for warning in &warnings {
writeln!(io::stderr(), " {}: {}", "Warning".yellow(), warning)?;
console_writeln!(stderr, " {}: {}", "Warning".yellow(), warning)?;
}
}
if self.options.output_style != OutputStyleOption::Disabled {
writeln!(io::stdout(), " ")?;
console_writeln!(io::stdout(), " ")?;
}
self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
+11 -9
View File
@@ -8,6 +8,7 @@ use std::io::{self, Write};
use crate::command::{Command, Commands};
use crate::export::ExportManager;
use crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder};
use crate::output::console_writeln;
use anyhow::Result;
@@ -80,15 +81,16 @@ impl<'a> Scheduler<'a> {
reference,
self.options.sort_order_speed_comparison,
) {
let mut stdout = io::stdout().lock();
match self.options.sort_order_speed_comparison {
SortOrder::MeanTime => {
writeln!(io::stdout(), "{}", "Summary".bold())?;
console_writeln!(stdout, "{}", "Summary".bold())?;
let reference = annotated_results.iter().find(|r| r.is_reference).unwrap();
let others = annotated_results.iter().filter(|r| !r.is_reference);
writeln!(
io::stdout(),
console_writeln!(
stdout,
" {} ran",
reference.result.command_with_unused_parameters.cyan()
)?;
@@ -116,8 +118,8 @@ impl<'a> Scheduler<'a> {
stddev
),
};
writeln!(
io::stdout(),
console_writeln!(
stdout,
"{} {}",
comparator,
item.result.command_with_unused_parameters.magenta()
@@ -125,11 +127,11 @@ impl<'a> Scheduler<'a> {
}
}
SortOrder::Command => {
writeln!(io::stdout(), "{}", "Relative speed comparison".bold())?;
console_writeln!(stdout, "{}", "Relative speed comparison".bold())?;
for item in annotated_results {
writeln!(
io::stdout(),
console_writeln!(
stdout,
" {}{} {}",
format!("{:10.2}", item.relative_speed).bold().green(),
if item.is_reference {
@@ -145,7 +147,7 @@ impl<'a> Scheduler<'a> {
}
}
} else {
writeln!(
console_writeln!(
io::stderr(),
"{}: The benchmark comparison could not be computed as some benchmark times are zero. \
This could be caused by background interference during the initial calibration phase \
+4
View File
@@ -3,6 +3,10 @@ use std::num::{self, ParseFloatError, ParseIntError};
use rust_decimal::Error as DecimalError;
use thiserror::Error;
#[derive(Debug, Error)]
#[error(transparent)]
pub struct ConsoleOutputError(pub std::io::Error);
#[derive(Debug, Error)]
pub enum ParameterScanError {
#[error("Error while parsing parameter scan arguments ({0})")]
+4 -2
View File
@@ -18,6 +18,7 @@ use self::orgmode::OrgmodeExporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::options::SortOrder;
use crate::output::console_writeln;
use crate::util::units::Unit;
use anyhow::{Context, Result};
@@ -144,8 +145,9 @@ impl ExportManager {
}
ExportTarget::Stdout => {
if !intermediate {
writeln!(io::stdout())?;
writeln!(io::stdout(), "{}", String::from_utf8(content()?).unwrap())?;
let mut stdout = io::stdout().lock();
console_writeln!(stdout)?;
console_writeln!(stdout, "{}", String::from_utf8(content()?).unwrap())?;
}
}
}
+7 -14
View File
@@ -9,6 +9,7 @@ use std::io::{self, Write};
use benchmark::scheduler::Scheduler;
use cli::get_cli_arguments;
use command::Commands;
use error::ConsoleOutputError;
use export::ExportManager;
use options::Options;
@@ -51,26 +52,18 @@ fn run() -> Result<()> {
Ok(())
}
/// Check whether `error` or one of its causes is an `io::Error` of kind
/// `BrokenPipe`. This happens when the process on the reading end of a
/// pipe (e.g. `hyperfine … | head -n 1`) terminates before hyperfine is
/// done writing its results.
fn caused_by_broken_pipe(error: &anyhow::Error) -> bool {
error.chain().any(|cause| {
cause
.downcast_ref::<io::Error>()
.is_some_and(|e| e.kind() == io::ErrorKind::BrokenPipe)
})
fn caused_by_broken_console_pipe(error: &anyhow::Error) -> bool {
error
.downcast_ref::<ConsoleOutputError>()
.is_some_and(|e| e.0.kind() == io::ErrorKind::BrokenPipe)
}
fn main() {
match run() {
Ok(_) => {}
Err(e) => {
// When writing to a closed pipe, exit quietly with a zero exit
// code — the same behavior that non-Rust CLI tools exhibit when
// they are terminated by `SIGPIPE`.
if caused_by_broken_pipe(&e) {
// Exit quietly when a reader stops consuming console output early.
if caused_by_broken_console_pipe(&e) {
std::process::exit(0);
}
// The write to stderr can itself fail if stderr is closed; the
+10
View File
@@ -1,3 +1,13 @@
pub mod format;
pub mod progress_bar;
pub mod warnings;
/// Mark console write errors so broken pipes can be handled separately from file errors.
macro_rules! console_writeln {
($writer:expr $(, $($args:tt)*)?) => {
writeln!($writer $(, $($args)*)?)
.map_err($crate::error::ConsoleOutputError)
};
}
pub(crate) use console_writeln;