mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
dd: fix precision for display of total time spent
Improve the display of the total time spent transferring bytes so that the number of seconds is displayed using the `%g` format specifier as in `printf`. This matches the behavior of GNU `dd`. Before this commit, the precision was always set to one digit after the decimal point. For example, $ dd count=100000 if=/dev/zero of=/dev/null 100000+0 records in 100000+0 records out 51200000 bytes (51 MB, 49 MiB) copied, 0.2 s, 268.1 MB/s After this commit, the precision increases dynamically as the duration decreases. For example, $ dd count=100000 if=/dev/zero of=/dev/null 100000+0 records in 100000+0 records out 51200000 bytes (51 MB, 49 MiB) copied, 0.1019 s, 507 MB/s $ dd count=1000 if=/dev/zero of=/dev/null 1000+0 records in 1000+0 records out 512000 bytes (512 kB, 500 KiB) copied, 0.002663 s, 256 MB/s $ dd count=10 if=/dev/zero of=/dev/null 10+0 records in 10+0 records out 5120 bytes (5.1 kB, 5.0 KiB) copied, 0.000182 s, 5.1 MB/s
This commit is contained in:
parent
7c0063ae3e
commit
2f56536637
4 changed files with 65 additions and 24 deletions
|
@ -18,7 +18,7 @@ path = "src/dd.rs"
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
gcd = { workspace=true }
|
gcd = { workspace=true }
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true, features=["memo"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||||
signal-hook = { workspace=true }
|
signal-hook = { workspace=true }
|
||||||
|
|
|
@ -13,6 +13,9 @@ use std::io::Write;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use uucore::error::UResult;
|
||||||
|
use uucore::memo::sprintf;
|
||||||
|
|
||||||
use crate::numbers::{to_magnitude_and_suffix, SuffixType};
|
use crate::numbers::{to_magnitude_and_suffix, SuffixType};
|
||||||
|
|
||||||
// On Linux, we register a signal handler that prints progress updates.
|
// On Linux, we register a signal handler that prints progress updates.
|
||||||
|
@ -131,7 +134,7 @@ impl ProgUpdate {
|
||||||
/// prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
/// prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
/// assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.0 s, 0.0 B/s\n");
|
/// assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.0 s, 0.0 B/s\n");
|
||||||
/// ```
|
/// ```
|
||||||
fn write_prog_line(&self, w: &mut impl Write, rewrite: bool) -> std::io::Result<()> {
|
fn write_prog_line(&self, w: &mut impl Write, rewrite: bool) -> UResult<()> {
|
||||||
// The total number of bytes written as a string, in SI and IEC format.
|
// The total number of bytes written as a string, in SI and IEC format.
|
||||||
let btotal = self.write_stat.bytes_total;
|
let btotal = self.write_stat.bytes_total;
|
||||||
let btotal_metric = to_magnitude_and_suffix(btotal, SuffixType::Si);
|
let btotal_metric = to_magnitude_and_suffix(btotal, SuffixType::Si);
|
||||||
|
@ -148,27 +151,31 @@ impl ProgUpdate {
|
||||||
// (`\n`) at the end.
|
// (`\n`) at the end.
|
||||||
let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") };
|
let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") };
|
||||||
|
|
||||||
|
// The duration should be formatted as in `printf %g`.
|
||||||
|
let duration_str = sprintf("%g", &[duration.to_string()])?;
|
||||||
|
|
||||||
// If the number of bytes written is sufficiently large, then
|
// If the number of bytes written is sufficiently large, then
|
||||||
// print a more concise representation of the number, like
|
// print a more concise representation of the number, like
|
||||||
// "1.2 kB" and "1.0 KiB".
|
// "1.2 kB" and "1.0 KiB".
|
||||||
match btotal {
|
match btotal {
|
||||||
1 => write!(
|
1 => write!(
|
||||||
w,
|
w,
|
||||||
"{carriage_return}{btotal} byte copied, {duration:.1} s, {transfer_rate}/s{newline}",
|
"{carriage_return}{btotal} byte copied, {duration_str} s, {transfer_rate}/s{newline}",
|
||||||
),
|
)?,
|
||||||
0..=999 => write!(
|
0..=999 => write!(
|
||||||
w,
|
w,
|
||||||
"{carriage_return}{btotal} bytes copied, {duration:.1} s, {transfer_rate}/s{newline}",
|
"{carriage_return}{btotal} bytes copied, {duration_str} s, {transfer_rate}/s{newline}",
|
||||||
),
|
)?,
|
||||||
1000..=1023 => write!(
|
1000..=1023 => write!(
|
||||||
w,
|
w,
|
||||||
"{carriage_return}{btotal} bytes ({btotal_metric}) copied, {duration:.1} s, {transfer_rate}/s{newline}",
|
"{carriage_return}{btotal} bytes ({btotal_metric}) copied, {duration_str} s, {transfer_rate}/s{newline}",
|
||||||
),
|
)?,
|
||||||
_ => write!(
|
_ => write!(
|
||||||
w,
|
w,
|
||||||
"{carriage_return}{btotal} bytes ({btotal_metric}, {btotal_bin}) copied, {duration:.1} s, {transfer_rate}/s{newline}",
|
"{carriage_return}{btotal} bytes ({btotal_metric}, {btotal_bin}) copied, {duration_str} s, {transfer_rate}/s{newline}",
|
||||||
),
|
)?,
|
||||||
}
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write all summary statistics.
|
/// Write all summary statistics.
|
||||||
|
@ -200,7 +207,7 @@ impl ProgUpdate {
|
||||||
/// assert_eq!(iter.next().unwrap(), b"");
|
/// assert_eq!(iter.next().unwrap(), b"");
|
||||||
/// assert!(iter.next().is_none());
|
/// assert!(iter.next().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
fn write_transfer_stats(&self, w: &mut impl Write, new_line: bool) -> std::io::Result<()> {
|
fn write_transfer_stats(&self, w: &mut impl Write, new_line: bool) -> UResult<()> {
|
||||||
if new_line {
|
if new_line {
|
||||||
writeln!(w)?;
|
writeln!(w)?;
|
||||||
}
|
}
|
||||||
|
@ -496,6 +503,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prog_update_duration(duration: Duration) -> ProgUpdate {
|
||||||
|
ProgUpdate {
|
||||||
|
read_stat: Default::default(),
|
||||||
|
write_stat: Default::default(),
|
||||||
|
duration: duration,
|
||||||
|
complete: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_stat_report() {
|
fn test_read_stat_report() {
|
||||||
let read_stat = ReadStat::new(1, 2, 3);
|
let read_stat = ReadStat::new(1, 2, 3);
|
||||||
|
@ -552,24 +568,24 @@ mod tests {
|
||||||
// 0 bytes copied, 7.9151e-05 s, 0.0 kB/s
|
// 0 bytes copied, 7.9151e-05 s, 0.0 kB/s
|
||||||
//
|
//
|
||||||
// The throughput still does not match GNU dd.
|
// The throughput still does not match GNU dd.
|
||||||
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.0 s, 0.0 B/s\n");
|
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1 s, 0.0 B/s\n");
|
||||||
|
|
||||||
let prog_update = prog_update_write(1);
|
let prog_update = prog_update_write(1);
|
||||||
let mut cursor = Cursor::new(vec![]);
|
let mut cursor = Cursor::new(vec![]);
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
assert_eq!(cursor.get_ref(), b"1 byte copied, 1.0 s, 0.0 B/s\n");
|
assert_eq!(cursor.get_ref(), b"1 byte copied, 1 s, 0.0 B/s\n");
|
||||||
|
|
||||||
let prog_update = prog_update_write(999);
|
let prog_update = prog_update_write(999);
|
||||||
let mut cursor = Cursor::new(vec![]);
|
let mut cursor = Cursor::new(vec![]);
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
assert_eq!(cursor.get_ref(), b"999 bytes copied, 1.0 s, 0.0 B/s\n");
|
assert_eq!(cursor.get_ref(), b"999 bytes copied, 1 s, 0.0 B/s\n");
|
||||||
|
|
||||||
let prog_update = prog_update_write(1000);
|
let prog_update = prog_update_write(1000);
|
||||||
let mut cursor = Cursor::new(vec![]);
|
let mut cursor = Cursor::new(vec![]);
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cursor.get_ref(),
|
cursor.get_ref(),
|
||||||
b"1000 bytes (1.0 kB) copied, 1.0 s, 1.0 kB/s\n"
|
b"1000 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
let prog_update = prog_update_write(1023);
|
let prog_update = prog_update_write(1023);
|
||||||
|
@ -577,7 +593,7 @@ mod tests {
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cursor.get_ref(),
|
cursor.get_ref(),
|
||||||
b"1023 bytes (1.0 kB) copied, 1.0 s, 1.0 kB/s\n"
|
b"1023 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
let prog_update = prog_update_write(1024);
|
let prog_update = prog_update_write(1024);
|
||||||
|
@ -585,7 +601,7 @@ mod tests {
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cursor.get_ref(),
|
cursor.get_ref(),
|
||||||
b"1024 bytes (1.0 kB, 1.0 KiB) copied, 1.0 s, 1.0 kB/s\n"
|
b"1024 bytes (1.0 kB, 1.0 KiB) copied, 1 s, 1.0 kB/s\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,7 +620,7 @@ mod tests {
|
||||||
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
|
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
|
||||||
assert_eq!(iter.next().unwrap(), b"0+0 records in");
|
assert_eq!(iter.next().unwrap(), b"0+0 records in");
|
||||||
assert_eq!(iter.next().unwrap(), b"0+0 records out");
|
assert_eq!(iter.next().unwrap(), b"0+0 records out");
|
||||||
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1.0 s, 0.0 B/s");
|
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
|
||||||
assert_eq!(iter.next().unwrap(), b"");
|
assert_eq!(iter.next().unwrap(), b"");
|
||||||
assert!(iter.next().is_none());
|
assert!(iter.next().is_none());
|
||||||
}
|
}
|
||||||
|
@ -623,11 +639,20 @@ mod tests {
|
||||||
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
prog_update.write_transfer_stats(&mut cursor, true).unwrap();
|
prog_update.write_transfer_stats(&mut cursor, true).unwrap();
|
||||||
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
|
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
|
||||||
assert_eq!(iter.next().unwrap(), b"\r0 bytes copied, 1.0 s, 0.0 B/s");
|
assert_eq!(iter.next().unwrap(), b"\r0 bytes copied, 1 s, 0.0 B/s");
|
||||||
assert_eq!(iter.next().unwrap(), b"0+0 records in");
|
assert_eq!(iter.next().unwrap(), b"0+0 records in");
|
||||||
assert_eq!(iter.next().unwrap(), b"0+0 records out");
|
assert_eq!(iter.next().unwrap(), b"0+0 records out");
|
||||||
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1.0 s, 0.0 B/s");
|
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
|
||||||
assert_eq!(iter.next().unwrap(), b"");
|
assert_eq!(iter.next().unwrap(), b"");
|
||||||
assert!(iter.next().is_none());
|
assert!(iter.next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_duration_precision() {
|
||||||
|
let prog_update = prog_update_duration(Duration::from_nanos(123));
|
||||||
|
let mut cursor = Cursor::new(vec![]);
|
||||||
|
let rewrite = false;
|
||||||
|
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
|
||||||
|
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.23e-07 s, 0.0 B/s\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -261,7 +263,9 @@ fn test_final_stats_noxfer() {
|
||||||
fn test_final_stats_unspec() {
|
fn test_final_stats_unspec() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.run()
|
.run()
|
||||||
.stderr_only("0+0 records in\n0+0 records out\n0 bytes copied, 0.0 s, 0.0 B/s\n")
|
.stderr_contains("0+0 records in\n0+0 records out\n0 bytes copied, ")
|
||||||
|
.stderr_matches(&Regex::new(r"\d\.\d+(e-\d\d)? s, ").unwrap())
|
||||||
|
.stderr_contains("0.0 B/s")
|
||||||
.success();
|
.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,9 +379,11 @@ fn test_existing_file_truncated() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_null_stats() {
|
fn test_null_stats() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["if=null.txt"])
|
.arg("if=null.txt")
|
||||||
.run()
|
.run()
|
||||||
.stderr_only("0+0 records in\n0+0 records out\n0 bytes copied, 0.0 s, 0.0 B/s\n")
|
.stderr_contains("0+0 records in\n0+0 records out\n0 bytes copied, ")
|
||||||
|
.stderr_matches(&Regex::new(r"\d\.\d+(e-\d\d)? s, ").unwrap())
|
||||||
|
.stderr_contains("0.0 B/s")
|
||||||
.success();
|
.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -692,6 +692,16 @@ impl CmdResult {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn stderr_matches(&self, regex: ®ex::Regex) -> &Self {
|
||||||
|
assert!(
|
||||||
|
regex.is_match(self.stderr_str()),
|
||||||
|
"Stderr does not match regex:\n{}",
|
||||||
|
self.stderr_str()
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &Self {
|
pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &Self {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue