Skip to content

Commit de0f566

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 975fe10 commit de0f566

9 files changed

Lines changed: 334 additions & 48 deletions

File tree

src/benchmark/executor.rs

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

149-
Ok((
150-
TimingResult {
151-
time_real: result.time_real,
152-
time_user: result.time_user,
153-
time_system: result.time_system,
154-
memory_usage_byte: result.memory_usage_byte,
155-
},
156-
result.status,
157-
))
149+
Ok((result.timing, result.status))
158150
}
159151

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

214206
// Subtract shell spawning time
215207
if let Some(spawning_time) = self.shell_spawning_time {
216-
result.time_real = (result.time_real - spawning_time.time_real).max(0.0);
217-
result.time_user = (result.time_user - spawning_time.time_user).max(0.0);
218-
result.time_system = (result.time_system - spawning_time.time_system).max(0.0);
208+
result.timing.time_real = (result.timing.time_real - spawning_time.time_real).max(0.0);
209+
result.timing.time_user = (result.timing.time_user - spawning_time.time_user).max(0.0);
210+
result.timing.time_system =
211+
(result.timing.time_system - spawning_time.time_system).max(0.0);
219212
}
220213

221-
Ok((
222-
TimingResult {
223-
time_real: result.time_real,
224-
time_user: result.time_user,
225-
time_system: result.time_system,
226-
memory_usage_byte: result.memory_usage_byte,
227-
},
228-
result.status,
229-
))
214+
Ok((result.timing, result.status))
230215
}
231216

232217
/// Measure the average shell spawning time
@@ -289,6 +274,12 @@ impl Executor for ShellExecutor<'_> {
289274
time_user: mean(&times_user),
290275
time_system: mean(&times_system),
291276
memory_usage_byte: 0,
277+
voluntary_context_switches: 0,
278+
context_switches: 0,
279+
filesystem_input: 0,
280+
filesystem_output: 0,
281+
minor_page_faults: 0,
282+
major_page_faults: 0,
292283
});
293284

294285
Ok(())
@@ -345,6 +336,12 @@ impl Executor for MockExecutor {
345336
time_user: 0.0,
346337
time_system: 0.0,
347338
memory_usage_byte: 0,
339+
voluntary_context_switches: 0,
340+
context_switches: 0,
341+
filesystem_input: 0,
342+
filesystem_output: 0,
343+
minor_page_faults: 0,
344+
major_page_faults: 0,
348345
},
349346
status,
350347
))

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
@@ -251,6 +251,12 @@ fn build_command() -> Command {
251251
the output of the tool.",
252252
),
253253
)
254+
.arg(
255+
Arg::new("resource-usage")
256+
.long("resource-usage")
257+
.action(ArgAction::SetTrue)
258+
.help("Show resource usage of the command.")
259+
)
254260
.arg(
255261
Arg::new("sort")
256262
.long("sort")

src/options.rs

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

232+
/// Display resource usage of commands
233+
pub resource_usage: bool,
234+
232235
/// How to order benchmarks in the relative speed comparison
233236
pub sort_order_speed_comparison: SortOrder,
234237

@@ -262,6 +265,7 @@ impl Default for Options {
262265
setup_command: None,
263266
cleanup_command: None,
264267
output_style: OutputStyleOption::Full,
268+
resource_usage: false,
265269
sort_order_speed_comparison: SortOrder::MeanTime,
266270
sort_order_exports: SortOrder::Command,
267271
executor_kind: ExecutorKind::default(),
@@ -382,6 +386,8 @@ impl Options {
382386
}
383387
};
384388

389+
options.resource_usage = *matches.get_one::<bool>("resource-usage").unwrap_or(&false);
390+
385391
match options.output_style {
386392
OutputStyleOption::Basic | OutputStyleOption::NoColor => {
387393
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)