Skip to content

Commit ade52a9

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 3cedcc3 commit ade52a9

9 files changed

Lines changed: 317 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
@@ -237,6 +237,12 @@ fn build_command() -> Command {
237237
the output of the tool.",
238238
),
239239
)
240+
.arg(
241+
Arg::new("resource-usage")
242+
.long("resource-usage")
243+
.action(ArgAction::SetTrue)
244+
.help("Show resource usage of the command.")
245+
)
240246
.arg(
241247
Arg::new("sort")
242248
.long("sort")

src/options.rs

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

226+
/// Display resource usage of commands
227+
pub resource_usage: bool,
228+
226229
/// How to order benchmarks in the relative speed comparison
227230
pub sort_order_speed_comparison: SortOrder,
228231

@@ -255,6 +258,7 @@ impl Default for Options {
255258
setup_command: None,
256259
cleanup_command: None,
257260
output_style: OutputStyleOption::Full,
261+
resource_usage: false,
258262
sort_order_speed_comparison: SortOrder::MeanTime,
259263
sort_order_exports: SortOrder::Command,
260264
executor_kind: ExecutorKind::default(),
@@ -373,6 +377,8 @@ impl Options {
373377
}
374378
};
375379

380+
options.resource_usage = *matches.get_one::<bool>("resource-usage").unwrap_or(&false);
381+
376382
match options.output_style {
377383
OutputStyleOption::Basic | OutputStyleOption::NoColor => {
378384
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)