mirror of
https://github.com/sharkdp/hyperfine.git
synced 2024-08-17 01:40:28 +03:00
Compare commits
18 Commits
171356267d
...
47810e602d
Author | SHA1 | Date | |
---|---|---|---|
|
47810e602d | ||
|
4a00f1821c | ||
|
981db9d102 | ||
|
ef1263279d | ||
|
e8ff88dad1 | ||
|
c6a6dccd95 | ||
|
d712879c9f | ||
|
c0921351e7 | ||
|
865b496098 | ||
|
51d056a3de | ||
|
836d1730b5 | ||
|
5c9d7a8b1e | ||
|
5493cf5039 | ||
|
6c774103d9 | ||
|
09c39d8989 | ||
|
96f8ae52ad | ||
|
ce6df59f0f | ||
|
39df4d821d |
3
.github/workflows/CICD.yml
vendored
3
.github/workflows/CICD.yml
vendored
@ -71,6 +71,7 @@ jobs:
|
||||
matrix:
|
||||
job:
|
||||
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
||||
- { target: aarch64-apple-darwin , os: macos-14 }
|
||||
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
|
||||
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
||||
- { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||
@ -325,7 +326,7 @@ jobs:
|
||||
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish archives and packages
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: steps.is-release.outputs.IS_RELEASE
|
||||
with:
|
||||
files: |
|
||||
|
26
Cargo.lock
generated
26
Cargo.lock
generated
@ -83,9 +83,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@ -300,11 +300,10 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.4"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
dependencies = [
|
||||
"is-terminal",
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@ -433,12 +432,6 @@ dependencies = [
|
||||
"ahash 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hyperfine"
|
||||
version = "1.18.0"
|
||||
@ -488,17 +481,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
|
@ -17,7 +17,7 @@ rust-version = "1.70.0"
|
||||
windows_process_extensions_main_thread_handle = []
|
||||
|
||||
[dependencies]
|
||||
colored = "2.0"
|
||||
colored = "2.1"
|
||||
indicatif = "=0.17.4"
|
||||
statistical = "1.0"
|
||||
csv = "1.1"
|
||||
|
33
README.md
33
README.md
@ -10,6 +10,21 @@ A command-line benchmarking tool.
|
||||
|
||||
![hyperfine](https://i.imgur.com/z19OYxE.gif)
|
||||
|
||||
## Sponsors
|
||||
|
||||
A special *thank you* goes to our biggest <a href="doc/sponsors.md">sponsor</a>:
|
||||
|
||||
<a href="https://bencher.dev/hyperfine/?utm_source=github&utm_medium=referral&utm_campaign=hyperfine&utm_content=wordmark">
|
||||
<img src="doc/sponsors/bencher_wordmark.svg" width="200" alt="🐰 Bencher">
|
||||
<br />
|
||||
<strong>Continuous Benchmarking: Catch performance regressions in CI</strong>
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Track the results of your <code>hyperfine</code> benchmarks over time with Bencher. \
|
||||
Detect and prevent performance regressions before they make it to production.
|
||||
|
||||
## Features
|
||||
|
||||
* Statistical analysis across multiple runs.
|
||||
@ -191,7 +206,7 @@ apk add hyperfine
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
On Arch Linux, hyperfine can be installed [from the official repositories](https://www.archlinux.org/packages/community/x86_64/hyperfine/):
|
||||
On Arch Linux, hyperfine can be installed [from the official repositories](https://archlinux.org/packages/extra/x86_64/hyperfine/):
|
||||
```
|
||||
pacman -S hyperfine
|
||||
```
|
||||
@ -203,6 +218,14 @@ On Debian Linux, hyperfine can be installed [from the testing repositories](http
|
||||
apt install hyperfine
|
||||
```
|
||||
|
||||
### On Exherbo Linux
|
||||
|
||||
On Exherbo Linux, hyperfine can be installed [from the rust repositories]([https://packages.debian.org/testing/main/hyperfine](https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/hyperfine)
|
||||
```
|
||||
cave resolve -x repository/rust
|
||||
cave resolve -x hyperfine
|
||||
```
|
||||
|
||||
### On Funtoo Linux
|
||||
|
||||
On Funtoo Linux, hyperfine can be installed [from core-kit](https://github.com/funtoo/core-kit/tree/1.4-release/app-benchmarks/hyperfine):
|
||||
@ -217,6 +240,14 @@ On NixOS, hyperfine can be installed [from the official repositories](https://ni
|
||||
nix-env -i hyperfine
|
||||
```
|
||||
|
||||
### On Flox
|
||||
|
||||
On Flox, hyperfine can be installed as follows.
|
||||
```
|
||||
flox install hyperfine
|
||||
```
|
||||
Hyperfine's version in Flox follows that of Nix.
|
||||
|
||||
### On openSUSE
|
||||
|
||||
On openSUSE, hyperfine can be installed [from the official repositories](https://software.opensuse.org/package/hyperfine):
|
||||
|
12
doc/sponsors.md
Normal file
12
doc/sponsors.md
Normal file
@ -0,0 +1,12 @@
|
||||
## Sponsors
|
||||
|
||||
`hyperfine` development is sponsored by many individuals and companies. Thank you very much!
|
||||
|
||||
Please note, that being sponsored does not affect the individuality of the `hyperfine`
|
||||
project or affect the maintainers' actions in any way.
|
||||
We remain impartial and continue to assess pull requests solely on merit - the
|
||||
features added, bugs solved, and effect on the overall complexity of the code.
|
||||
No issue will have a different priority based on sponsorship status of the
|
||||
reporter.
|
||||
|
||||
If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).
|
80
doc/sponsors/bencher_wordmark.svg
Normal file
80
doc/sponsors/bencher_wordmark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
@ -15,6 +15,7 @@ import matplotlib.pyplot as plt
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("file", help="JSON file with benchmark results")
|
||||
parser.add_argument("--title", help="Plot Title")
|
||||
parser.add_argument("--sort-by", choices=['median'], help="Sort method")
|
||||
parser.add_argument(
|
||||
"--labels", help="Comma-separated list of entries for the plot legend"
|
||||
)
|
||||
@ -24,7 +25,7 @@ parser.add_argument(
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.file) as f:
|
||||
with open(args.file, encoding='utf-8') as f:
|
||||
results = json.load(f)["results"]
|
||||
|
||||
if args.labels:
|
||||
@ -33,6 +34,13 @@ else:
|
||||
labels = [b["command"] for b in results]
|
||||
times = [b["times"] for b in results]
|
||||
|
||||
if args.sort_by == 'median':
|
||||
medians = [b["median"] for b in results]
|
||||
indices = sorted(range(len(labels)), key=lambda k: medians[k])
|
||||
labels = [labels[i] for i in indices]
|
||||
times = [times[i] for i in indices]
|
||||
|
||||
plt.figure(figsize=(10, 6), constrained_layout=True)
|
||||
boxplot = plt.boxplot(times, vert=True, patch_artist=True)
|
||||
cmap = plt.get_cmap("rainbow")
|
||||
colors = [cmap(val / len(times)) for val in range(len(times))]
|
||||
@ -45,6 +53,7 @@ if args.title:
|
||||
plt.legend(handles=boxplot["boxes"], labels=labels, loc="best", fontsize="medium")
|
||||
plt.ylabel("Time [s]")
|
||||
plt.ylim(0, None)
|
||||
plt.xticks(list(range(1, len(labels)+1)), labels, rotation=45)
|
||||
if args.output:
|
||||
plt.savefig(args.output)
|
||||
else:
|
||||
|
@ -54,7 +54,7 @@ fn run_command_and_measure_common(
|
||||
);
|
||||
|
||||
let result = execute_and_measure(command)
|
||||
.with_context(|| format!("Failed to run command '{}'", command_name))?;
|
||||
.with_context(|| format!("Failed to run command '{command_name}'"))?;
|
||||
|
||||
if command_failure_action == CmdFailureAction::RaiseError && !result.status.success() {
|
||||
bail!(
|
||||
@ -62,7 +62,7 @@ fn run_command_and_measure_common(
|
||||
Alternatively, use the '--show-output' option to debug what went wrong.",
|
||||
result.status.code().map_or(
|
||||
"The process has been terminated by a signal".into(),
|
||||
|c| format!("Command terminated with non-zero exit code: {}", c)
|
||||
|c| format!("Command terminated with non-zero exit code: {c}")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ impl<'a> Benchmark<'a> {
|
||||
let (mean_str, time_unit) = format_duration_unit(t_mean, self.options.time_unit);
|
||||
let min_str = format_duration(t_min, Some(time_unit));
|
||||
let max_str = format_duration(t_max, Some(time_unit));
|
||||
let num_str = format!("{} runs", t_num);
|
||||
let num_str = format!("{t_num} runs");
|
||||
|
||||
let user_str = format_duration(user_mean, Some(time_unit));
|
||||
let system_str = format_duration(system_mean, Some(time_unit));
|
||||
|
@ -86,7 +86,7 @@ impl<'a> Scheduler<'a> {
|
||||
"{}{} times faster than {}",
|
||||
format!("{:8.2}", item.relative_speed).bold().green(),
|
||||
if let Some(stddev) = item.relative_speed_stddev {
|
||||
format!(" ± {}", format!("{:.2}", stddev).green())
|
||||
format!(" ± {}", format!("{stddev:.2}").green())
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
@ -104,7 +104,7 @@ impl<'a> Scheduler<'a> {
|
||||
if item.is_fastest {
|
||||
" ".into()
|
||||
} else if let Some(stddev) = item.relative_speed_stddev {
|
||||
format!(" ± {}", format!("{:5.2}", stddev).green())
|
||||
format!(" ± {}", format!("{stddev:5.2}").green())
|
||||
} else {
|
||||
" ".into()
|
||||
},
|
||||
|
@ -61,13 +61,14 @@ impl<'a> Command<'a> {
|
||||
pub fn get_name_with_unused_parameters(&self) -> String {
|
||||
let parameters = self
|
||||
.get_unused_parameters()
|
||||
.map(|(parameter, value)| format!("{} = {}, ", parameter, value.to_string()))
|
||||
.collect::<String>();
|
||||
.fold(String::new(), |output, (parameter, value)| {
|
||||
output + &format!("{parameter} = {value}, ")
|
||||
});
|
||||
let parameters = parameters.trim_end_matches(", ");
|
||||
let parameters = if parameters.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(" ({})", parameters)
|
||||
format!(" ({parameters})")
|
||||
};
|
||||
|
||||
format!("{}{}", self.get_name(), parameters)
|
||||
@ -80,7 +81,7 @@ impl<'a> Command<'a> {
|
||||
pub fn get_command(&self) -> Result<std::process::Command> {
|
||||
let command_line = self.get_command_line();
|
||||
let mut tokens = shell_words::split(&command_line)
|
||||
.with_context(|| format!("Failed to parse command '{}'", command_line))?
|
||||
.with_context(|| format!("Failed to parse command '{command_line}'"))?
|
||||
.into_iter();
|
||||
|
||||
if let Some(program_name) = tokens.next() {
|
||||
@ -99,17 +100,14 @@ impl<'a> Command<'a> {
|
||||
pub fn get_unused_parameters(&self) -> impl Iterator<Item = &(&'a str, ParameterValue)> {
|
||||
self.parameters
|
||||
.iter()
|
||||
.filter(move |(parameter, _)| !self.expression.contains(&format!("{{{}}}", parameter)))
|
||||
.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();
|
||||
for (param_name, param_value) in &self.parameters {
|
||||
replacements.insert(
|
||||
format!("{{{param_name}}}", param_name = param_name),
|
||||
param_value.to_string(),
|
||||
);
|
||||
replacements.insert(format!("{{{param_name}}}"), param_value.to_string());
|
||||
}
|
||||
let mut remaining = original;
|
||||
// Manually replace consecutive occurrences to avoid double-replacing: e.g.,
|
||||
@ -319,7 +317,6 @@ impl<'a> Commands<'a> {
|
||||
let command_names = command_names.map_or(vec![], |names| {
|
||||
names.map(|v| v.as_str()).collect::<Vec<_>>()
|
||||
});
|
||||
let command_strings = command_strings;
|
||||
let param_name = vals.next().unwrap().as_str();
|
||||
let param_min = vals.next().unwrap().as_str();
|
||||
let param_max = vals.next().unwrap().as_str();
|
||||
|
@ -32,7 +32,7 @@ impl MarkupExporter for AsciidocExporter {
|
||||
}
|
||||
|
||||
fn command(&self, cmd: &str) -> String {
|
||||
format!("`{}`", cmd)
|
||||
format!("`{cmd}`")
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +71,7 @@ fn test_asciidoc_exporter_table_header() {
|
||||
#[cfg(test)]
|
||||
fn cfg_test_table_header(unit_short_name: &str) -> String {
|
||||
format!(
|
||||
"[cols=\"<,>,>,>,>\"]\n|===\n| Command \n| Mean [{unit}] \n| Min [{unit}] \n| Max [{unit}] \n| Relative \n",
|
||||
unit = unit_short_name
|
||||
"[cols=\"<,>,>,>,>\"]\n|===\n| Command \n| Mean [{unit_short_name}] \n| Min [{unit_short_name}] \n| Max [{unit_short_name}] \n| Relative \n"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl Exporter for CsvExporter {
|
||||
.collect();
|
||||
if let Some(res) = results.first() {
|
||||
for param_name in res.parameters.keys() {
|
||||
headers.push(Cow::Owned(format!("parameter_{}", param_name).into_bytes()));
|
||||
headers.push(Cow::Owned(format!("parameter_{param_name}").into_bytes()));
|
||||
}
|
||||
}
|
||||
writer.write_record(headers)?;
|
||||
|
@ -24,7 +24,7 @@ impl MarkupExporter for MarkdownExporter {
|
||||
}
|
||||
|
||||
fn command(&self, cmd: &str) -> String {
|
||||
format!("`{}`", cmd)
|
||||
format!("`{cmd}`")
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,8 +53,7 @@ fn test_markdown_formatter_table_divider() {
|
||||
#[cfg(test)]
|
||||
fn cfg_test_table_header(unit_short_name: String) -> String {
|
||||
format!(
|
||||
"| Command | Mean [{unit}] | Min [{unit}] | Max [{unit}] | Relative |\n|:---|---:|---:|---:|---:|\n",
|
||||
unit = unit_short_name
|
||||
"| Command | Mean [{unit_short_name}] | Min [{unit_short_name}] | Max [{unit_short_name}] | Relative |\n|:---|---:|---:|---:|---:|\n"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -32,9 +32,9 @@ pub trait MarkupExporter {
|
||||
// emit table header data
|
||||
table.push_str(&self.table_row(&[
|
||||
"Command",
|
||||
&format!("Mean {}", notation),
|
||||
&format!("Min {}", notation),
|
||||
&format!("Max {}", notation),
|
||||
&format!("Mean {notation}"),
|
||||
&format!("Min {notation}"),
|
||||
&format!("Max {notation}"),
|
||||
"Relative",
|
||||
]));
|
||||
|
||||
@ -59,7 +59,7 @@ pub trait MarkupExporter {
|
||||
let rel_stddev_str = if entry.is_fastest {
|
||||
"".into()
|
||||
} else if let Some(stddev) = entry.relative_speed_stddev {
|
||||
format!(" ± {:.2}", stddev)
|
||||
format!(" ± {stddev:.2}")
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
@ -67,10 +67,10 @@ pub trait MarkupExporter {
|
||||
// prepare table row entries
|
||||
table.push_str(&self.table_row(&[
|
||||
&self.command(&cmd_str),
|
||||
&format!("{}{}", mean_str, stddev_str),
|
||||
&format!("{mean_str}{stddev_str}"),
|
||||
&min_str,
|
||||
&max_str,
|
||||
&format!("{}{}", rel_str, rel_stddev_str),
|
||||
&format!("{rel_str}{rel_stddev_str}"),
|
||||
]))
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ impl ExportManager {
|
||||
ExportTarget::Stdout
|
||||
} else {
|
||||
let _ = File::create(filename)
|
||||
.with_context(|| format!("Could not create export file '{}'", filename))?;
|
||||
.with_context(|| format!("Could not create export file '{filename}'"))?;
|
||||
ExportTarget::File(filename.to_string())
|
||||
},
|
||||
});
|
||||
@ -153,5 +153,5 @@ impl ExportManager {
|
||||
fn write_to_file(filename: &str, content: &[u8]) -> Result<()> {
|
||||
let mut file = OpenOptions::new().write(true).open(filename)?;
|
||||
file.write_all(content)
|
||||
.with_context(|| format!("Failed to export results to '{}'", filename))
|
||||
.with_context(|| format!("Failed to export results to '{filename}'"))
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ impl MarkupExporter for OrgmodeExporter {
|
||||
}
|
||||
|
||||
fn command(&self, cmd: &str) -> String {
|
||||
format!("={}=", cmd)
|
||||
format!("={cmd}=")
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,8 +58,7 @@ fn test_orgmode_formatter_table_line() {
|
||||
#[cfg(test)]
|
||||
fn cfg_test_table_header(unit_short_name: String) -> String {
|
||||
format!(
|
||||
"| Command | Mean [{unit}] | Min [{unit}] | Max [{unit}] | Relative |\n|--+--+--+--+--|\n",
|
||||
unit = unit_short_name
|
||||
"| Command | Mean [{unit_short_name}] | Min [{unit_short_name}] | Max [{unit_short_name}] | Relative |\n|--+--+--+--+--|\n"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ impl Default for Shell {
|
||||
impl fmt::Display for Shell {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Shell::Default(cmd) => write!(f, "{}", cmd),
|
||||
Shell::Default(cmd) => write!(f, "{cmd}"),
|
||||
Shell::Custom(cmdline) => write!(f, "{}", shell_words::join(cmdline)),
|
||||
}
|
||||
}
|
||||
@ -457,7 +457,7 @@ impl Options {
|
||||
fn test_default_shell() {
|
||||
let shell = Shell::default();
|
||||
|
||||
let s = format!("{}", shell);
|
||||
let s = format!("{shell}");
|
||||
assert_eq!(&s, DEFAULT_SHELL);
|
||||
|
||||
let cmd = shell.command();
|
||||
@ -468,7 +468,7 @@ fn test_default_shell() {
|
||||
fn test_can_parse_shell_command_line_from_str() {
|
||||
let shell = Shell::parse_from_str("shell -x 'aaa bbb'").unwrap();
|
||||
|
||||
let s = format!("{}", shell);
|
||||
let s = format!("{shell}");
|
||||
assert_eq!(&s, "shell -x 'aaa bbb'");
|
||||
|
||||
let cmd = shell.command();
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::util::number::Number;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub mod range_step;
|
||||
pub mod tokenize;
|
||||
@ -9,12 +10,13 @@ pub enum ParameterValue {
|
||||
Numeric(Number),
|
||||
}
|
||||
|
||||
impl ToString for ParameterValue {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
impl Display for ParameterValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let str = match self {
|
||||
ParameterValue::Text(ref value) => value.clone(),
|
||||
ParameterValue::Numeric(value) => value.to_string(),
|
||||
}
|
||||
};
|
||||
write!(f, "{str}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// A max function for f64's without NaNs
|
||||
pub fn max(vals: &[f64]) -> f64 {
|
||||
*vals
|
||||
|
@ -26,7 +26,7 @@ impl Unit {
|
||||
/// Returns the Second value formatted for the Unit.
|
||||
pub fn format(self, value: Second) -> String {
|
||||
match self {
|
||||
Unit::Second => format!("{:.3}", value),
|
||||
Unit::Second => format!("{value:.3}"),
|
||||
Unit::MilliSecond => format!("{:.1}", value * 1e3),
|
||||
Unit::MicroSecond => format!("{:.1}", value * 1e6),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user