Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions src/benchmark/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,7 @@ impl Executor for RawExecutor<'_> {
&command.get_command_line(),
)?;

Ok((
TimingResult {
time_real: result.time_real,
time_user: result.time_user,
time_system: result.time_system,
memory_usage_byte: result.memory_usage_byte,
},
result.status,
))
Ok((result.timing, result.status))
}

fn calibrate(&mut self) -> Result<()> {
Expand Down Expand Up @@ -213,20 +205,13 @@ impl Executor for ShellExecutor<'_> {

// Subtract shell spawning time
if let Some(spawning_time) = self.shell_spawning_time {
result.time_real = (result.time_real - spawning_time.time_real).max(0.0);
result.time_user = (result.time_user - spawning_time.time_user).max(0.0);
result.time_system = (result.time_system - spawning_time.time_system).max(0.0);
result.timing.time_real = (result.timing.time_real - spawning_time.time_real).max(0.0);
result.timing.time_user = (result.timing.time_user - spawning_time.time_user).max(0.0);
result.timing.time_system =
(result.timing.time_system - spawning_time.time_system).max(0.0);
}

Ok((
TimingResult {
time_real: result.time_real,
time_user: result.time_user,
time_system: result.time_system,
memory_usage_byte: result.memory_usage_byte,
},
result.status,
))
Ok((result.timing, result.status))
}

/// Measure the average shell spawning time
Expand Down Expand Up @@ -289,6 +274,12 @@ impl Executor for ShellExecutor<'_> {
time_user: mean(&times_user),
time_system: mean(&times_system),
memory_usage_byte: 0,
voluntary_context_switches: 0,
context_switches: 0,
filesystem_input: 0,
filesystem_output: 0,
minor_page_faults: 0,
major_page_faults: 0,
});

Ok(())
Expand Down Expand Up @@ -345,6 +336,12 @@ impl Executor for MockExecutor {
time_user: 0.0,
time_system: 0.0,
memory_usage_byte: 0,
voluntary_context_switches: 0,
context_switches: 0,
filesystem_input: 0,
filesystem_output: 0,
minor_page_faults: 0,
major_page_faults: 0,
},
status,
))
Expand Down
80 changes: 76 additions & 4 deletions src/benchmark/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ use crate::options::{
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
};
use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
use crate::output::format::{format_duration, format_duration_unit};
use crate::output::format::{format_duration, format_duration_unit, BytesFormat};
use crate::output::progress_bar::get_progress_bar;
use crate::output::warnings::{OutlierWarningOptions, Warnings};
use crate::parameter::ParameterNameAndValue;
use crate::util::exit_code::extract_exit_code;
use crate::util::min_max::{max, min};
use crate::util::min_max::{max, min, statistics};
use crate::util::units::Second;
use benchmark_result::BenchmarkResult;
use timing_result::TimingResult;
Expand Down Expand Up @@ -154,6 +154,12 @@ impl<'a> Benchmark<'a> {
let mut memory_usage_byte: Vec<u64> = vec![];
let mut exit_codes: Vec<Option<i32>> = vec![];
let mut all_succeeded = true;
let mut voluntary_context_switches = Vec::new();
let mut context_switches = Vec::new();
let mut filesystem_input = Vec::new();
let mut filesystem_output = Vec::new();
let mut minor_page_faults = Vec::new();
let mut major_page_faults = Vec::new();

let output_policy = &self.options.command_output_policies[self.number];

Expand Down Expand Up @@ -282,6 +288,14 @@ impl<'a> Benchmark<'a> {
times_system.push(res.time_system);
memory_usage_byte.push(res.memory_usage_byte);
exit_codes.push(extract_exit_code(status));
if self.options.resource_usage {
voluntary_context_switches.push(res.voluntary_context_switches);
context_switches.push(res.context_switches);
filesystem_input.push(res.filesystem_input);
filesystem_output.push(res.filesystem_output);
minor_page_faults.push(res.minor_page_faults);
major_page_faults.push(res.major_page_faults);
}

all_succeeded = all_succeeded && success;

Expand Down Expand Up @@ -319,6 +333,14 @@ impl<'a> Benchmark<'a> {
times_system.push(res.time_system);
memory_usage_byte.push(res.memory_usage_byte);
exit_codes.push(extract_exit_code(status));
if self.options.resource_usage {
voluntary_context_switches.push(res.voluntary_context_switches);
context_switches.push(res.context_switches);
filesystem_input.push(res.filesystem_input);
filesystem_output.push(res.filesystem_output);
minor_page_faults.push(res.minor_page_faults);
major_page_faults.push(res.major_page_faults);
}

all_succeeded = all_succeeded && success;

Expand Down Expand Up @@ -389,6 +411,56 @@ impl<'a> Benchmark<'a> {
num_str.dimmed()
);
}

if self.options.resource_usage {
println!();

macro_rules! print_bytes_stats {
($stats: expr, $title: literal) => {{
let stats = statistics(&$stats);
println!(
" {:<7} ({} … {}|{} … {}): {:>8} … {:>8}{}{:<8} … {:>8}",
$title,
"min".cyan(),
"mean".green().bold(),
"med".blue().bold(),
"max".purple(),
format!("{}", BytesFormat(stats.min)).cyan(),
format!("{}", BytesFormat(stats.mean)).green().bold(),
"|".dimmed(),
format!("{}", BytesFormat(stats.median)).blue().bold(),
format!("{}", BytesFormat(stats.max)).purple()
);
}};
}

macro_rules! print_stats {
($stats: expr, $title: literal) => {{
let stats = statistics(&$stats);
println!(
" {:<7} ({} … {}|{} … {}): {:>8} … {:>8}{}{:<8} … {:>8}",
$title,
"min".cyan(),
"mean".green().bold(),
"med".blue().bold(),
"max".purple(),
format!("{}", stats.min).cyan(),
format!("{}", stats.mean).green().bold(),
"|".dimmed(),
format!("{}", stats.median).blue().bold(),
format!("{}", stats.max).purple()
);
}};
}

print_bytes_stats!(memory_usage_byte, "maxrss");
print_stats!(voluntary_context_switches, "nvcsw");
print_stats!(context_switches, "nivcsw");
print_stats!(filesystem_input, "inblock");
print_stats!(filesystem_output, "oublock");
print_stats!(minor_page_faults, "minflt");
print_stats!(major_page_faults, "majflt");
}
}

// Warnings
Expand Down Expand Up @@ -430,15 +502,15 @@ impl<'a> Benchmark<'a> {
}

if !warnings.is_empty() {
eprintln!(" ");
eprintln!();

for warning in &warnings {
eprintln!(" {}: {}", "Warning".yellow(), warning);
}
}

if self.options.output_style != OutputStyleOption::Disabled {
println!(" ");
println!();
}

self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
Expand Down
18 changes: 18 additions & 0 deletions src/benchmark/timing_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,22 @@ pub struct TimingResult {

/// Maximum amount of memory used, in bytes
pub memory_usage_byte: u64,

/// Number of voluntary context switches
pub voluntary_context_switches: u64,

/// Number of involuntary context switches
pub context_switches: u64,

/// Number of times the filesystem had to perform input.
pub filesystem_input: u64,

/// Number of times the filesystem had to perform output.
pub filesystem_output: u64,

/// Number of minor page faults
pub minor_page_faults: u64,

/// Number of major page faults
pub major_page_faults: u64,
}
6 changes: 6 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@ fn build_command() -> Command {
the output of the tool.",
),
)
.arg(
Arg::new("resource-usage")
.long("resource-usage")
.action(ArgAction::SetTrue)
.help("Show resource usage of the command.")
)
.arg(
Arg::new("sort")
.long("sort")
Expand Down
6 changes: 6 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ pub struct Options {
/// What color mode to use for the terminal output
pub output_style: OutputStyleOption,

/// Display resource usage of commands
pub resource_usage: bool,

/// How to order benchmarks in the relative speed comparison
pub sort_order_speed_comparison: SortOrder,

Expand Down Expand Up @@ -262,6 +265,7 @@ impl Default for Options {
setup_command: None,
cleanup_command: None,
output_style: OutputStyleOption::Full,
resource_usage: false,
sort_order_speed_comparison: SortOrder::MeanTime,
sort_order_exports: SortOrder::Command,
executor_kind: ExecutorKind::default(),
Expand Down Expand Up @@ -382,6 +386,8 @@ impl Options {
}
};

options.resource_usage = *matches.get_one::<bool>("resource-usage").unwrap_or(&false);

match options.output_style {
OutputStyleOption::Basic | OutputStyleOption::NoColor => {
colored::control::set_override(false)
Expand Down
58 changes: 58 additions & 0 deletions src/output/format.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use crate::util::units::{Second, Unit};

/// Format the given duration as a string. The output-unit can be enforced by setting `unit` to
Expand Down Expand Up @@ -25,6 +27,51 @@ pub fn format_duration_value(duration: Second, unit: Option<Unit>) -> (String, U
}
}

/// Wrapper to format memory sizes as a string.
pub struct BytesFormat(pub u64);

impl Display for BytesFormat {
#[expect(clippy::cast_precision_loss)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes = self.0;
if bytes < 100_000 {
return write!(f, "{bytes} b");
}
let bytes = bytes as f64 / 1000.0;
if bytes < 10.0 {
return write!(f, "{bytes:.3} kb");
}
if bytes < 100.0 {
return write!(f, "{bytes:.2} kb");
}
if bytes < 1000.0 {
return write!(f, "{bytes:.1} kb");
}
let bytes = bytes / 1000.0;
if bytes < 10.0 {
return write!(f, "{bytes:.3} MB");
}
if bytes < 100.0 {
return write!(f, "{bytes:.2} MB");
}
if bytes < 1000.0 {
return write!(f, "{bytes:.1} MB");
}
let bytes = bytes / 1000.0;
if bytes < 10.0 {
return write!(f, "{bytes:.3} GB");
}
if bytes < 100.0 {
return write!(f, "{bytes:.2} GB");
}
if bytes < 1000.0 {
return write!(f, "{bytes:.1} GB");
}
let bytes = bytes / 1000.0;
write!(f, "{bytes:.0} TB")
}
}

#[test]
fn test_format_duration_unit_basic() {
let (out_str, out_unit) = format_duration_unit(1.3, None);
Expand Down Expand Up @@ -75,3 +122,14 @@ fn test_format_duration_unit_with_unit() {
assert_eq!("1300000.0 µs", out_str);
assert_eq!(Unit::MicroSecond, out_unit);
}

#[test]
fn test_format_bytes() {
assert_eq!("0 b", format!("{}", BytesFormat(0)));
assert_eq!("42 b", format!("{}", BytesFormat(42)));
assert_eq!("10240 b", format!("{}", BytesFormat(10240)));
assert_eq!("102.4 kb", format!("{}", BytesFormat(102400)));
assert_eq!("102.4 MB", format!("{}", BytesFormat(102400000)));
assert_eq!("1.024 GB", format!("{}", BytesFormat(1024000000)));
assert_eq!("18446744 TB", format!("{}", BytesFormat(u64::MAX)));
}
Loading