Skip to content

Commit 0451698

Browse files
committed
Add option to show resource usage
Show the information of getrusage(2) returned via wait4(2). Also get the maximum RSS usage from wait4(2), since getrusage(2) will for the lifetime of the hyperfine process always remember the highest children, not the last one.
1 parent 16348dc commit 0451698

9 files changed

Lines changed: 321 additions & 49 deletions

File tree

src/benchmark/executor.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,7 @@ impl Executor for RawExecutor<'_> {
128128
&command.get_command_line(),
129129
)?;
130130

131-
Ok((
132-
TimingResult {
133-
time_real: result.time_real,
134-
time_user: result.time_user,
135-
time_system: result.time_system,
136-
memory_usage_byte: result.memory_usage_byte,
137-
},
138-
result.status,
139-
))
131+
Ok((result.timing, result.status))
140132
}
141133

142134
fn calibrate(&mut self) -> Result<()> {
@@ -195,20 +187,13 @@ impl Executor for ShellExecutor<'_> {
195187

196188
// Subtract shell spawning time
197189
if let Some(spawning_time) = self.shell_spawning_time {
198-
result.time_real = (result.time_real - spawning_time.time_real).max(0.0);
199-
result.time_user = (result.time_user - spawning_time.time_user).max(0.0);
200-
result.time_system = (result.time_system - spawning_time.time_system).max(0.0);
190+
result.timing.time_real = (result.timing.time_real - spawning_time.time_real).max(0.0);
191+
result.timing.time_user = (result.timing.time_user - spawning_time.time_user).max(0.0);
192+
result.timing.time_system =
193+
(result.timing.time_system - spawning_time.time_system).max(0.0);
201194
}
202195

203-
Ok((
204-
TimingResult {
205-
time_real: result.time_real,
206-
time_user: result.time_user,
207-
time_system: result.time_system,
208-
memory_usage_byte: result.memory_usage_byte,
209-
},
210-
result.status,
211-
))
196+
Ok((result.timing, result.status))
212197
}
213198

214199
/// Measure the average shell spawning time
@@ -271,6 +256,12 @@ impl Executor for ShellExecutor<'_> {
271256
time_user: mean(&times_user),
272257
time_system: mean(&times_system),
273258
memory_usage_byte: 0,
259+
voluntary_context_switches: 0,
260+
context_switches: 0,
261+
filesystem_input: 0,
262+
filesystem_output: 0,
263+
minor_page_faults: 0,
264+
major_page_faults: 0,
274265
});
275266

276267
Ok(())
@@ -327,6 +318,12 @@ impl Executor for MockExecutor {
327318
time_user: 0.0,
328319
time_system: 0.0,
329320
memory_usage_byte: 0,
321+
voluntary_context_switches: 0,
322+
context_switches: 0,
323+
filesystem_input: 0,
324+
filesystem_output: 0,
325+
minor_page_faults: 0,
326+
major_page_faults: 0,
330327
},
331328
status,
332329
))

src/benchmark/mod.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ use crate::options::{
1212
CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,
1313
};
1414
use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};
15-
use crate::output::format::{format_duration, format_duration_unit};
15+
use crate::output::format::{format_duration, format_duration_unit, BytesFormat};
1616
use crate::output::progress_bar::get_progress_bar;
1717
use crate::output::warnings::{OutlierWarningOptions, Warnings};
1818
use crate::parameter::ParameterNameAndValue;
1919
use crate::util::exit_code::extract_exit_code;
20-
use crate::util::min_max::{max, min};
20+
use crate::util::min_max::{max, min, statistics};
2121
use crate::util::units::Second;
2222
use benchmark_result::BenchmarkResult;
2323
use timing_result::TimingResult;
@@ -154,6 +154,12 @@ impl<'a> Benchmark<'a> {
154154
let mut memory_usage_byte: Vec<u64> = vec![];
155155
let mut exit_codes: Vec<Option<i32>> = vec![];
156156
let mut all_succeeded = true;
157+
let mut voluntary_context_switches = Vec::new();
158+
let mut context_switches = Vec::new();
159+
let mut filesystem_input = Vec::new();
160+
let mut filesystem_output = Vec::new();
161+
let mut minor_page_faults = Vec::new();
162+
let mut major_page_faults = Vec::new();
157163

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

@@ -282,6 +288,14 @@ impl<'a> Benchmark<'a> {
282288
times_system.push(res.time_system);
283289
memory_usage_byte.push(res.memory_usage_byte);
284290
exit_codes.push(extract_exit_code(status));
291+
if self.options.resource_usage {
292+
voluntary_context_switches.push(res.voluntary_context_switches);
293+
context_switches.push(res.context_switches);
294+
filesystem_input.push(res.filesystem_input);
295+
filesystem_output.push(res.filesystem_output);
296+
minor_page_faults.push(res.minor_page_faults);
297+
major_page_faults.push(res.major_page_faults);
298+
}
285299

286300
all_succeeded = all_succeeded && success;
287301

@@ -319,6 +333,14 @@ impl<'a> Benchmark<'a> {
319333
times_system.push(res.time_system);
320334
memory_usage_byte.push(res.memory_usage_byte);
321335
exit_codes.push(extract_exit_code(status));
336+
if self.options.resource_usage {
337+
voluntary_context_switches.push(res.voluntary_context_switches);
338+
context_switches.push(res.context_switches);
339+
filesystem_input.push(res.filesystem_input);
340+
filesystem_output.push(res.filesystem_output);
341+
minor_page_faults.push(res.minor_page_faults);
342+
major_page_faults.push(res.major_page_faults);
343+
}
322344

323345
all_succeeded = all_succeeded && success;
324346

@@ -389,6 +411,56 @@ impl<'a> Benchmark<'a> {
389411
num_str.dimmed()
390412
);
391413
}
414+
415+
if self.options.resource_usage {
416+
println!();
417+
418+
macro_rules! print_bytes_stats {
419+
($stats: expr, $title: literal) => {{
420+
let stats = statistics(&$stats);
421+
println!(
422+
" {:<7} ({} … {}|{} … {}): {:>8} … {:>8}{}{:<8} … {:>8}",
423+
$title,
424+
"min".cyan(),
425+
"mean".green().bold(),
426+
"med".blue().bold(),
427+
"max".purple(),
428+
format!("{}", BytesFormat(stats.min)).cyan(),
429+
format!("{}", BytesFormat(stats.mean)).green().bold(),
430+
"|".dimmed(),
431+
format!("{}", BytesFormat(stats.median)).blue().bold(),
432+
format!("{}", BytesFormat(stats.max)).purple()
433+
);
434+
}};
435+
}
436+
437+
macro_rules! print_stats {
438+
($stats: expr, $title: literal) => {{
439+
let stats = statistics(&$stats);
440+
println!(
441+
" {:<7} ({} … {}|{} … {}): {:>8} … {:>8}{}{:<8} … {:>8}",
442+
$title,
443+
"min".cyan(),
444+
"mean".green().bold(),
445+
"med".blue().bold(),
446+
"max".purple(),
447+
format!("{}", stats.min).cyan(),
448+
format!("{}", stats.mean).green().bold(),
449+
"|".dimmed(),
450+
format!("{}", stats.median).blue().bold(),
451+
format!("{}", stats.max).purple()
452+
);
453+
}};
454+
}
455+
456+
print_bytes_stats!(memory_usage_byte, "maxrss");
457+
print_stats!(voluntary_context_switches, "nvcsw");
458+
print_stats!(context_switches, "nivcsw");
459+
print_stats!(filesystem_input, "inblock");
460+
print_stats!(filesystem_output, "oublock");
461+
print_stats!(minor_page_faults, "minflt");
462+
print_stats!(major_page_faults, "majflt");
463+
}
392464
}
393465

394466
// Warnings
@@ -430,15 +502,15 @@ impl<'a> Benchmark<'a> {
430502
}
431503

432504
if !warnings.is_empty() {
433-
eprintln!(" ");
505+
eprintln!();
434506

435507
for warning in &warnings {
436508
eprintln!(" {}: {}", "Warning".yellow(), warning);
437509
}
438510
}
439511

440512
if self.options.output_style != OutputStyleOption::Disabled {
441-
println!(" ");
513+
println!();
442514
}
443515

444516
self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;

src/benchmark/timing_result.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,22 @@ pub struct TimingResult {
1414

1515
/// Maximum amount of memory used, in bytes
1616
pub memory_usage_byte: u64,
17+
18+
/// Number of voluntary context switches
19+
pub voluntary_context_switches: u64,
20+
21+
/// Number of involuntary context switches
22+
pub context_switches: u64,
23+
24+
/// Number of times the filesystem had to perform input.
25+
pub filesystem_input: u64,
26+
27+
/// Number of times the filesystem had to perform output.
28+
pub filesystem_output: u64,
29+
30+
/// Number of minor page faults
31+
pub minor_page_faults: u64,
32+
33+
/// Number of major page faults
34+
pub major_page_faults: u64,
1735
}

src/cli.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ fn build_command() -> Command {
245245
the output of the tool.",
246246
),
247247
)
248+
.arg(
249+
Arg::new("resource-usage")
250+
.long("resource-usage")
251+
.action(ArgAction::SetTrue)
252+
.help("Show resource usage of the command.")
253+
)
248254
.arg(
249255
Arg::new("sort")
250256
.long("sort")

src/options.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ pub struct Options {
226226
/// What color mode to use for the terminal output
227227
pub output_style: OutputStyleOption,
228228

229+
/// Display resource usage of commands
230+
pub resource_usage: bool,
231+
229232
/// How to order benchmarks in the relative speed comparison
230233
pub sort_order_speed_comparison: SortOrder,
231234

@@ -259,6 +262,7 @@ impl Default for Options {
259262
setup_command: None,
260263
cleanup_command: None,
261264
output_style: OutputStyleOption::Full,
265+
resource_usage: false,
262266
sort_order_speed_comparison: SortOrder::MeanTime,
263267
sort_order_exports: SortOrder::Command,
264268
executor_kind: ExecutorKind::default(),
@@ -379,6 +383,8 @@ impl Options {
379383
}
380384
};
381385

386+
options.resource_usage = *matches.get_one::<bool>("resource-usage").unwrap_or(&false);
387+
382388
match options.output_style {
383389
OutputStyleOption::Basic | OutputStyleOption::NoColor => {
384390
colored::control::set_override(false)

src/output/format.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::fmt::Display;
2+
13
use crate::util::units::{Second, Unit};
24

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

30+
/// Wrapper to format memory sizes as a string.
31+
pub struct BytesFormat(pub u64);
32+
33+
impl Display for BytesFormat {
34+
#[expect(clippy::cast_precision_loss)]
35+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36+
let bytes = self.0;
37+
if bytes < 100_000 {
38+
return write!(f, "{bytes} b");
39+
}
40+
let bytes = bytes as f64 / 1000.0;
41+
if bytes < 10.0 {
42+
return write!(f, "{bytes:.3} kb");
43+
}
44+
if bytes < 100.0 {
45+
return write!(f, "{bytes:.2} kb");
46+
}
47+
if bytes < 1000.0 {
48+
return write!(f, "{bytes:.1} kb");
49+
}
50+
let bytes = bytes / 1000.0;
51+
if bytes < 10.0 {
52+
return write!(f, "{bytes:.3} MB");
53+
}
54+
if bytes < 100.0 {
55+
return write!(f, "{bytes:.2} MB");
56+
}
57+
if bytes < 1000.0 {
58+
return write!(f, "{bytes:.1} MB");
59+
}
60+
let bytes = bytes / 1000.0;
61+
if bytes < 10.0 {
62+
return write!(f, "{bytes:.3} GB");
63+
}
64+
if bytes < 100.0 {
65+
return write!(f, "{bytes:.2} GB");
66+
}
67+
if bytes < 1000.0 {
68+
return write!(f, "{bytes:.1} GB");
69+
}
70+
let bytes = bytes / 1000.0;
71+
write!(f, "{bytes:.0} TB")
72+
}
73+
}
74+
2875
#[test]
2976
fn test_format_duration_unit_basic() {
3077
let (out_str, out_unit) = format_duration_unit(1.3, None);
@@ -75,3 +122,14 @@ fn test_format_duration_unit_with_unit() {
75122
assert_eq!("1300000.0 µs", out_str);
76123
assert_eq!(Unit::MicroSecond, out_unit);
77124
}
125+
126+
#[test]
127+
fn test_format_bytes() {
128+
assert_eq!("0 b", format!("{}", BytesFormat(0)));
129+
assert_eq!("42 b", format!("{}", BytesFormat(42)));
130+
assert_eq!("10240 b", format!("{}", BytesFormat(10240)));
131+
assert_eq!("102.4 kb", format!("{}", BytesFormat(102400)));
132+
assert_eq!("102.4 MB", format!("{}", BytesFormat(102400000)));
133+
assert_eq!("1.024 GB", format!("{}", BytesFormat(1024000000)));
134+
assert_eq!("18446744 TB", format!("{}", BytesFormat(u64::MAX)));
135+
}

0 commit comments

Comments
 (0)