df: round the total row once, from the summed bytes - #14522
Conversation
The total row added up the block counts of the rows, each of which is already rounded up to the block size, instead of rounding the summed bytes once the way GNU df does. With a block size larger than the filesystems, every non-empty row is one block, so the total became a count of filesystems, and the human-readable output then multiplied that count back by the block size and overflowed. Keep the raw byte counts in the rows, sum those for the total row, and round at output time like every other row. Fixes uutils#14459
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate findings remain in total formatting and regression-test assertions.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates df --total to sum raw bytes before rounding, matching GNU behavior and avoiding large-block-size overflow.
Changes:
- Accumulates raw byte totals and rounds once at output.
- Adds regression coverage for rounding and human-readable large block sizes.
- Updates integration expectations for per-row rounding differences.
File summaries
| File | Summary |
|---|---|
tests/by-util/test_df.rs |
Updates total assertions and adds regression coverage; exact-value, deterministic-filesystem, and versioned GNU-reference adjustments remain requested. |
src/uu/df/src/table.rs |
Aggregates raw bytes and formats totals; explicit -B with -h still needs GNU-compatible precedence and output handling. |
Review details
Suppressed comments (3)
src/uu/df/src/table.rs:270
- When
-his combined with an explicit-B, this branch still formats the raw total as human-readable and ignores the requested block size. For the linked issue's reproducer GNU prints1block, while this remains an E-suffixed byte value; the new integration test only checks that it does not panic. Make the-B/-hprecedence consistent with GNU and assert the resulting output.
let s = if let Some(h) = self.options.human_readable {
to_magnitude_and_suffix(bytes.into(), SuffixType::HumanReadable(h), true)
} else {
let BlockSize::Bytes(d) = self.options.block_size;
bytes.div_ceil(d).to_string()
tests/by-util/test_df.rs:502
- The regression test says the total should be one block, but
<= 1also accepts zero, so a broken or missing total accumulator could still pass. Assert the exact expected value.
assert!(total <= 1, "total row reports {total} blocks");
tests/by-util/test_df.rs:490
- This command sums every mounted filesystem, so a fixed 10-exabyte block size does not guarantee a one-block total: on a host whose included filesystems exceed 10 EB, correct output is 2 or more and this test fails. Restrict the command to a known filesystem (for example, append
.) and assert the expected result, or use only the deterministic table fixture for this regression.
"--block-size=10000000000000000000",
])
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Run them on the filesystem of the current directory only, so the one row and the total are both exactly one block, and name the GNU version the rounding was compared against.
There was a problem hiding this comment.
🟡 Changes recommended
A critical total-formatting issue and a regression-test coverage gap remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
tests/by-util/test_df.rs:496
- This test only invokes
dffor., so it normally has one filesystem row. The removedBytesCellimplementation would also print1for both that row and the total, meaning this assertion does not exercise rounding across rows. Use a deterministic multi-filesystem fixture (or make the command-level test assert a real multi-row case) so the regression can fail again.
let values: Vec<&str> = output.lines().skip(1).map(str::trim).collect();
assert_eq!(values, vec!["1", "1"]);
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
| bytes_column.bytes | ||
| }; | ||
| to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h), true) | ||
| to_magnitude_and_suffix(bytes.into(), SuffixType::HumanReadable(h), true) |
|
GNU testsuite comparison: |
Merging this PR will improve performance by 3.66%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | du_all_wide_tree[(5000, 500)] |
38.6 ms | 37.2 ms | +3.66% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing TanbirRamim:df-total-round-once (8ff66fb) with main (4bae306)2
Footnotes
-
50 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
main(92076ab) during the generation of this report, so 4bae306 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
df --totalsummed the block counts of the rows, each already rounded up to the block size, instead of rounding the summed bytes once like GNU df does. With-B 100Gon my machine GNU reports a total of 19 blocks where the rows add up to 24; we printed 24. With a block size larger than every filesystem the total degenerates into a count of filesystems, and-hthen multiplied that count back by the block size and overflowed (attempt to multiply with overflow, exit 134).This goes back to keeping the raw byte counts in the rows and rounding at output time. #10438 had moved away from that so the total would match the sum of the displayed rows, but GNU does not do that either, so
test_totalnow allows for the rounding difference.Fixes #14459