Implement --output={null,pipe,<FILE>}

Fixes #377.
This commit is contained in:
Tavian Barnes authored and David Peter committed 2022-05-17 22:30:45 +02:00
1 parent f75f0a10fe
commit 277807cb5a
6 files changed
+129 -23

No files matched your search

Generated
+22
View File
@@ -274,6 +274,7 @@ dependencies = [
"csv", "csv",
"indicatif", "indicatif",
"libc", "libc",
"nix",
"predicates", "predicates",
"rand 0.8.5", "rand 0.8.5",
"rust_decimal", "rust_decimal",
@@ -356,6 +357,27 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "nix"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "normalize-line-endings" name = "normalize-line-endings"
version = "0.3.0" version = "0.3.0"
+3
View File
@@ -31,6 +31,9 @@ libc = "0.2"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["processthreadsapi", "minwindef", "winnt"] } winapi = { version = "0.3", features = ["processthreadsapi", "minwindef", "winnt"] }
[target.'cfg(target_os="linux")'.dependencies]
nix = { version = "0.24.1", features = ["zerocopy"] }
[dependencies.clap] [dependencies.clap]
version = "3" version = "3"
default-features = false default-features = false
+4 -4
View File
@@ -36,10 +36,10 @@ pub trait Executor {
fn run_command_and_measure_common( fn run_command_and_measure_common(
mut command: std::process::Command, mut command: std::process::Command,
command_failure_action: CmdFailureAction, command_failure_action: CmdFailureAction,
command_output_policy: CommandOutputPolicy, command_output_policy: &CommandOutputPolicy,
command_name: &str, command_name: &str,
) -> Result<TimerResult> { ) -> Result<TimerResult> {
let (stdout, stderr) = command_output_policy.get_stdout_stderr(); let (stdout, stderr) = command_output_policy.get_stdout_stderr()?;
command.stdin(Stdio::null()).stdout(stdout).stderr(stderr); command.stdin(Stdio::null()).stdout(stdout).stderr(stderr);
command.env( command.env(
@@ -83,7 +83,7 @@ impl<'a> Executor for RawExecutor<'a> {
let result = run_command_and_measure_common( let result = run_command_and_measure_common(
command.get_command()?, command.get_command()?,
command_failure_action.unwrap_or(self.options.command_failure_action), command_failure_action.unwrap_or(self.options.command_failure_action),
self.options.command_output_policy, &self.options.command_output_policy,
&command.get_command_line(), &command.get_command_line(),
)?; )?;
@@ -136,7 +136,7 @@ impl<'a> Executor for ShellExecutor<'a> {
let mut result = run_command_and_measure_common( let mut result = run_command_and_measure_common(
command_builder, command_builder,
command_failure_action.unwrap_or(self.options.command_failure_action), command_failure_action.unwrap_or(self.options.command_failure_action),
self.options.command_output_policy, &self.options.command_output_policy,
&command.get_command_line(), &command.get_command_line(),
)?; )?;
+14
View File
@@ -259,6 +259,20 @@ fn build_command() -> Command<'static> {
when trying to benchmark output speed.", when trying to benchmark output speed.",
), ),
) )
.arg(
Arg::new("output")
.long("output")
.conflicts_with("show-output")
.takes_value(true)
.value_name("WHERE")
.help(
"Control where the output of the benchmark is redirected.\n\n \
null: Redirect output to /dev/null (the default).\n\n \
pipe: Feed the output through a pipe before discarding it.\n \
Useful if the benchmark special-cases /dev/null.\n\n \
<FILE>: Write the output to the given file.",
),
)
.arg( .arg(
Arg::new("command-name") Arg::new("command-name")
.long("command-name") .long("command-name")
+31 -6
View File
@@ -1,5 +1,7 @@
use std::fs::File;
use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::{cmp, fmt}; use std::{cmp, fmt, io};
use anyhow::ensure; use anyhow::ensure;
use atty::Stream; use atty::Stream;
@@ -109,11 +111,17 @@ impl Default for RunBounds {
} }
/// How to handle the output of benchmarked commands /// How to handle the output of benchmarked commands
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum CommandOutputPolicy { pub enum CommandOutputPolicy {
/// Discard all output /// Discard all output
Discard, Discard,
/// Feed output through a pipe before discarding it
Pipe,
/// Redirect output to a file
File(PathBuf),
/// Show command output on the terminal /// Show command output on the terminal
Forward, Forward,
} }
@@ -125,11 +133,22 @@ impl Default for CommandOutputPolicy {
} }
impl CommandOutputPolicy { impl CommandOutputPolicy {
pub fn get_stdout_stderr(&self) -> (Stdio, Stdio) { pub fn get_stdout_stderr(&self) -> io::Result<(Stdio, Stdio)> {
match self { let streams = match self {
CommandOutputPolicy::Discard => (Stdio::null(), Stdio::null()), CommandOutputPolicy::Discard => (Stdio::null(), Stdio::null()),
// Typically only stdout is performance-relevant, so just pipe that
CommandOutputPolicy::Pipe => (Stdio::piped(), Stdio::null()),
CommandOutputPolicy::File(path) => {
let file = File::create(&path)?;
(file.into(), Stdio::null())
}
CommandOutputPolicy::Forward => (Stdio::inherit(), Stdio::inherit()), CommandOutputPolicy::Forward => (Stdio::inherit(), Stdio::inherit()),
} };
Ok(streams)
} }
} }
@@ -251,6 +270,12 @@ impl Options {
options.command_output_policy = if matches.is_present("show-output") { options.command_output_policy = if matches.is_present("show-output") {
CommandOutputPolicy::Forward CommandOutputPolicy::Forward
} else if let Some(output) = matches.value_of("output") {
match output {
"null" => CommandOutputPolicy::Discard,
"pipe" => CommandOutputPolicy::Pipe,
path => CommandOutputPolicy::File(path.into()),
}
} else { } else {
CommandOutputPolicy::Discard CommandOutputPolicy::Discard
}; };
@@ -262,7 +287,7 @@ impl Options {
Some("color") => OutputStyleOption::Color, Some("color") => OutputStyleOption::Color,
Some("none") => OutputStyleOption::Disabled, Some("none") => OutputStyleOption::Disabled,
_ => { _ => {
if options.command_output_policy == CommandOutputPolicy::Discard if options.command_output_policy != CommandOutputPolicy::Forward
&& atty::is(Stream::Stdout) && atty::is(Stream::Stdout)
{ {
OutputStyleOption::Full OutputStyleOption::Full
+55 -13
View File
@@ -6,10 +6,20 @@ mod windows_timer;
#[cfg(not(windows))] #[cfg(not(windows))]
mod unix_timer; mod unix_timer;
#[cfg(target_os = "linux")]
use nix::fcntl::{splice, SpliceFFlags};
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::os::unix::io::AsRawFd;
#[cfg(not(target_os = "linux"))]
use std::io::Read;
use crate::util::units::Second; use crate::util::units::Second;
use wall_clock_timer::WallClockTimer; use wall_clock_timer::WallClockTimer;
use std::process::{Command, ExitStatus}; use std::process::{ChildStdout, Command, ExitStatus};
use anyhow::Result; use anyhow::Result;
@@ -33,26 +43,58 @@ pub struct TimerResult {
pub status: ExitStatus, pub status: ExitStatus,
} }
/// Discard the output of a child process.
fn discard(output: ChildStdout) {
const CHUNK_SIZE: usize = 64 << 10;
#[cfg(target_os = "linux")]
{
if let Ok(file) = File::create("/dev/null") {
while let Ok(bytes) = splice(
output.as_raw_fd(),
None,
file.as_raw_fd(),
None,
CHUNK_SIZE,
SpliceFFlags::empty(),
) {
if bytes == 0 {
break;
}
}
}
}
#[cfg(not(target_os = "linux"))]
{
let mut output = output;
let mut buf = [0; CHUNK_SIZE];
while let Ok(bytes) = output.read(&mut buf) {
if bytes == 0 {
break;
}
}
}
}
/// Execute the given command and return a timing summary /// Execute the given command and return a timing summary
pub fn execute_and_measure(mut command: Command) -> Result<TimerResult> { pub fn execute_and_measure(mut command: Command) -> Result<TimerResult> {
let wallclock_timer = WallClockTimer::start(); let wallclock_timer = WallClockTimer::start();
let mut child = command.spawn()?;
let output = child.stdout.take();
#[cfg(not(windows))] #[cfg(not(windows))]
let ((time_user, time_system), status) = { let cpu_timer = self::unix_timer::CPUTimer::start();
let cpu_timer = self::unix_timer::CPUTimer::start();
let status = command.status()?;
(cpu_timer.stop(), status)
};
#[cfg(windows)] #[cfg(windows)]
let ((time_user, time_system), status) = { let cpu_timer = self::windows_timer::CPUTimer::start_for_process(&child);
let mut child = command.spawn()?;
let cpu_timer = self::windows_timer::CPUTimer::start_for_process(&child);
let status = child.wait()?;
(cpu_timer.stop(), status) if let Some(output) = output {
}; discard(output);
}
let status = child.wait()?;
let (time_user, time_system) = cpu_timer.stop();
let time_real = wallclock_timer.stop(); let time_real = wallclock_timer.stop();
Ok(TimerResult { Ok(TimerResult {