Repository: sharkdp/hyperfine (language: rust)

Issue:
Display parameter values if they aren't used in the benchmarked command
Not sure what the UI should look like, but if you only use parameter values in a prepare command, you currently have no way of knowing which benchmarks used which parameters.


Discussion / hints:
Thank you. This sounds very reasonable.

> Not sure what the UI should look like

Any help would be appreciated.
A suggestion:

```
Benchmark 1: command args
  Params: x='foo' y='bar'
  Time (mean ± σ):      3.246 s ±  0.023 s    [User: 2.903 s, System: 0.250 s]
  Range (min … max):    3.213 s …  3.267 s    5 runs

Benchmark 2: command args
  Params: x='bar' y='foo'
  Time (mean ± σ):      2.448 s ±  0.080 s    [User: 2.119 s, System: 0.239 s]
  Range (min … max):    2.362 s …  2.564 s    5 runs

Summary
  'command args' with x='foo' y='bar' ran
    1.33 ± 0.04 times faster than 'command args' with x='bar' y='foo'
```

Fix patch:
diff --git a/src/benchmark/benchmark_result.rs b/src/benchmark/benchmark_result.rs
index 26714c7a3..b37f72544 100644
--- a/src/benchmark/benchmark_result.rs
+++ b/src/benchmark/benchmark_result.rs
@@ -12,6 +12,11 @@ pub struct BenchmarkResult {
     /// The full command line of the program that is being benchmarked
     pub command: String,
 
+    /// The full command line of the program that is being benchmarked, possibly including a list of
+    /// parameters that were not used in the command line template.
+    #[serde(skip_serializing)]
+    pub command_with_unused_parameters: String,
+
     /// The average run time
     pub mean: Second,
 
diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs
index 00402a379..6728cc14d 100644
--- a/src/benchmark/mod.rs
+++ b/src/benchmark/mod.rs
@@ -112,13 +112,12 @@ impl<'a> Benchmark<'a> {
 
     /// Run the benchmark for a single command
     pub fn run(&self) -> Result<BenchmarkResult> {
-        let command_name = self.command.get_name();
         if self.options.output_style != OutputStyleOption::Disabled {
             println!(
                 "{}{}: {}",
                 "Benchmark ".bold(),
                 (self.number + 1).to_string().bold(),
-                command_name,
+                self.command.get_name_with_unused_parameters(),
             );
         }
 
@@ -369,7 +368,8 @@ impl<'a> Benchmark<'a> {
         self.run_cleanup_command(self.command.get_parameters().iter().cloned())?;
 
         Ok(BenchmarkResult {
-            command: command_name,
+            command: self.command.get_name(),
+            command_with_unused_parameters: self.command.get_name_with_unused_parameters(),
             mean: t_mean,
             stddev: t_stddev,
             median: t_median,
diff --git a/src/benchmark/relative_speed.rs b/src/benchmark/relative_speed.rs
index ad9ad5a3a..d7a842d89 100644
--- a/src/benchmark/relative_speed.rs
+++ b/src/benchmark/relative_speed.rs
@@ -89,6 +89,7 @@ fn create_result(name: &str, mean: Scalar) -> BenchmarkResult {
 
     BenchmarkResult {
         command: name.into(),
+        command_with_unused_parameters: name.into(),
         mean,
         stddev: Some(1.0),
         median: mean,
diff --git a/src/benchmark/scheduler.rs b/src/benchmark/scheduler.rs
index 1c86f4a23..ced48338a 100644
--- a/src/benchmark/scheduler.rs
+++ b/src/benchmark/scheduler.rs
@@ -68,18 +68,21 @@ impl<'a> Scheduler<'a> {
             let others = &annotated_results[1..];
 
             println!("{}", "Summary".bold());
-            println!("  '{}' ran", fastest.result.command.cyan());
+            println!(
+                "  {} ran",
+                fastest.result.command_with_unused_parameters.cyan()
+            );
 
             for item in others {
                 println!(
-                    "{}{} times faster than '{}'",
+                    "{}{} times faster than {}",
                     format!("{:8.2}", item.relative_speed).bold().green(),
                     if let Some(stddev) = item.relative_speed_stddev {
                         format!(" ± {}", format!("{:.2}", stddev).green())
                     } else {
                         "".into()
                     },
-                    &item.result.command.magenta()
+                    &item.result.command_with_unused_parameters.magenta()
                 );
             }
         } else {
diff --git a/src/command.rs b/src/command.rs
index 2ce651fce..4a984067e 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
 use std::fmt;
 use std::str::FromStr;
 
+use crate::parameter::tokenize::tokenize;
+use crate::parameter::ParameterValue;
 use crate::{
     error::{OptionsError, ParameterScanError},
     parameter::{
@@ -12,9 +14,6 @@ use crate::{
 
 use clap::{parser::ValuesRef, ArgMatches};
 
-use crate::parameter::tokenize::tokenize;
-use crate::parameter::ParameterValue;
-
 use anyhow::{bail, Context, Result};
 use rust_decimal::Decimal;
 
@@ -59,6 +58,23 @@ impl<'a> Command<'a> {
         )
     }
 
+    pub fn get_name_with_unused_parameters(&self) -> String {
+        let parameters = self
+            .get_unused_parameters()
+            .map(|(parameter, value)| {
+                format!("{} = {}, ", parameter.to_string(), value.to_string())
+            })
+            .collect::<String>();
+        let parameters = parameters.trim_end_matches(", ");
+        let parameters = if parameters.is_empty() {
+            "".into()
+        } else {
+            format!(" ({})", parameters)
+        };
+
+        format!("{}{}", self.get_name(), parameters)
+    }
+
     pub fn get_command_line(&self) -> String {
         self.replace_parameters_in(self.expression)
     }
@@ -82,6 +98,12 @@ impl<'a> Command<'a> {
         &self.parameters
     }
 
+    pub fn get_unused_parameters(&self) -> impl Iterator<Item = &(&'a str, ParameterValue)> {
+        self.parameters
+            .iter()
+            .filter(move |(parameter, _)| !self.expression.contains(&format!("{{{}}}", parameter)))
+    }
+
     fn replace_parameters_in(&self, original: &str) -> String {
         let mut result = String::new();
         let mut replacements = BTreeMap::<String, String>::new();
diff --git a/src/export/asciidoc.rs b/src/export/asciidoc.rs
index 3b5be53dd..9133d6c6a 100644
--- a/src/export/asciidoc.rs
+++ b/src/export/asciidoc.rs
@@ -94,6 +94,7 @@ fn test_asciidoc_format_s() {
     let results = vec![
         BenchmarkResult {
             command: String::from("FOO=1 BAR=2 command | 1"),
+            command_with_unused_parameters: String::from("FOO=1 BAR=2 command | 1"),
             mean: 1.0,
             stddev: Some(2.0),
             median: 1.0,
@@ -112,6 +113,7 @@ fn test_asciidoc_format_s() {
         },
         BenchmarkResult {
             command: String::from("FOO=1 BAR=7 command | 2"),
+            command_with_unused_parameters: String::from("FOO=1 BAR=7 command | 2"),
             mean: 11.0,
             stddev: Some(12.0),
             median: 11.0,
@@ -163,6 +165,7 @@ fn test_asciidoc_format_ms() {
     let results = vec![
         BenchmarkResult {
             command: String::from("FOO=1 BAR=7 command | 2"),
+            command_with_unused_parameters: String::from("FOO=1 BAR=7 command | 2"),
             mean: 0.011,
             stddev: Some(0.012),
             median: 0.011,
@@ -181,6 +184,7 @@ fn test_asciidoc_format_ms() {
         },
         BenchmarkResult {
             command: String::from("FOO=1 BAR=2 command | 1"),
+            command_with_unused_parameters: String::from("FOO=1 BAR=2 command | 1"),
             mean: 1.0,
             stddev: Some(2.0),
             median: 1.0,
diff --git a/src/export/csv.rs b/src/export/csv.rs
index f550f5f10..c846a9508 100644
--- a/src/export/csv.rs
+++ b/src/export/csv.rs
@@ -62,6 +62,7 @@ fn test_csv() {
     let results = vec![
         BenchmarkResult {
             command: String::from("FOO=one BAR=two command | 1"),
+            command_with_unused_parameters: String::from("FOO=one BAR=two command | 1"),
             mean: 1.0,
             stddev: Some(2.0),
             median: 1.0,
@@ -80,6 +81,7 @@ fn test_csv() {
         },
         BenchmarkResult {
             command: String::from("FOO=one BAR=seven command | 2"),
+            command_with_unused_parameters: String::from("FOO=one BAR=seven command | 2"),
             mean: 11.0,
             stddev: Some(12.0),
             median: 11.0,
diff --git a/src/export/markdown.rs b/src/export/markdown.rs
index 44651c328..ba6623b45 100644
--- a/src/export/markdown.rs
+++ b/src/export/markdown.rs
@@ -71,6 +71,7 @@ fn test_markdown_format_ms() {
     let timing_results = vec![
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -84,6 +85,7 @@ fn test_markdown_format_ms() {
         },
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -121,6 +123,7 @@ fn test_markdown_format_s() {
     let timing_results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -134,6 +137,7 @@ fn test_markdown_format_s() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -171,6 +175,7 @@ fn test_markdown_format_time_unit_s() {
     let timing_results = vec![
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -184,6 +189,7 @@ fn test_markdown_format_time_unit_s() {
         },
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -227,6 +233,7 @@ fn test_markdown_format_time_unit_ms() {
     let timing_results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -240,6 +247,7 @@ fn test_markdown_format_time_unit_ms() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
diff --git a/src/export/markup.rs b/src/export/markup.rs
index 6984225d6..c789f3abf 100644
--- a/src/export/markup.rs
+++ b/src/export/markup.rs
@@ -43,7 +43,9 @@ pub trait MarkupExporter {
         for entry in entries {
             let measurement = &entry.result;
             // prepare data row strings
-            let cmd_str = measurement.command.replace('|', "\\|");
+            let cmd_str = measurement
+                .command_with_unused_parameters
+                .replace('|', "\\|");
             let mean_str = format_duration_value(measurement.mean, Some(unit)).0;
             let stddev_str = if let Some(stddev) = measurement.stddev {
                 format!(" ± {}", format_duration_value(stddev, Some(unit)).0)
@@ -119,6 +121,7 @@ fn test_determine_unit_from_results_unit_given_s() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -132,6 +135,7 @@ fn test_determine_unit_from_results_unit_given_s() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -159,6 +163,7 @@ fn test_determine_unit_from_results_unit_given_ms() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -172,6 +177,7 @@ fn test_determine_unit_from_results_unit_given_ms() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -199,6 +205,7 @@ fn test_determine_unit_from_results_unit_first_s() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -212,6 +219,7 @@ fn test_determine_unit_from_results_unit_first_s() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -239,6 +247,7 @@ fn test_determine_unit_from_results_unit_first_ms() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -252,6 +261,7 @@ fn test_determine_unit_from_results_unit_first_ms() {
         },
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
diff --git a/src/export/orgmode.rs b/src/export/orgmode.rs
index 42c15e527..8ec879299 100644
--- a/src/export/orgmode.rs
+++ b/src/export/orgmode.rs
@@ -77,6 +77,7 @@ fn test_orgmode_format_ms() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,
@@ -90,6 +91,7 @@ fn test_orgmode_format_ms() {
         },
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -129,6 +131,7 @@ fn test_orgmode_format_s() {
     let results = vec![
         BenchmarkResult {
             command: String::from("sleep 2"),
+            command_with_unused_parameters: String::from("sleep 2"),
             mean: 2.0050,
             stddev: Some(0.0020),
             median: 2.0050,
@@ -142,6 +145,7 @@ fn test_orgmode_format_s() {
         },
         BenchmarkResult {
             command: String::from("sleep 0.1"),
+            command_with_unused_parameters: String::from("sleep 0.1"),
             mean: 0.1057,
             stddev: Some(0.0016),
             median: 0.1057,


Test patch:
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 7930d8d30..ed5cdc583 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -382,3 +382,19 @@ fn exports_intermediate_results_to_file() {
     let contents = std::fs::read_to_string(export_path).unwrap();
     assert!(contents.contains("true"));
 }
+
+#[test]
+fn unused_parameters_are_shown_in_benchmark_name() {
+    hyperfine()
+        .arg("--runs=2")
+        .arg("--parameter-list")
+        .arg("branch")
+        .arg("master,feature")
+        .arg("echo test")
+        .assert()
+        .success()
+        .stdout(
+            predicate::str::contains("echo test (branch = master)")
+                .and(predicate::str::contains("echo test (branch = feature)")),
+        );
+}