Add 4 KiB logical block footprint tracking to stats - #1070
Conversation
This adds a 'Number of modified 4K blocks' metric to the --stats output to decouple network delta payload from actual disk I/O. Previously, a small amount of literal data scattered across a file (especially with --inplace) could result in a massive number of disjointed disk writes with no visibility, and large sparse files masked their true physical storage footprint. Technical details: - Implemented a stateful block tracker in the receiver that calculates touched 4K boundaries using file offsets and lengths, including strict lseek awareness to accurately skip sparse file holes. - Enforced strict per-file lifetime state with a reset hook inside receive_data(), successfully mitigating POSIX file descriptor (FD) recycling state leaks. - Created MSG_BLOCK_STATS multiplex message to tunnel the block footprint safely out of the isolated receiver process and relay it over the network. - Bumped PROTOCOL_VERSION to 33 and SUBPROTOCOL_VERSION to 8392 for safe PR testing. - Added test suite covering contiguous, scattered, zero-byte, sparse file (hole-skipping), multi-file (FD reuse), batch mode, and maximum-I/O boundary conditions.
8801271 to
59ef172
Compare
| if (len <= 0) | ||
| return; | ||
| extern struct stats stats; | ||
| static int last_tracked_fd = -1; |
There was a problem hiding this comment.
File-descriptor numbers are reusable so a later file can receive the same descriptor and inherit last_touched_blk from the previous file. The counter then under-reports that files initial ranges. More generally, one “last block” cannot count unique blocks when writes revisit or move backwards. This needs per-file lifetime state and a representation capable of tracking non-monotonic ranges.
There was a problem hiding this comment.
Yeah totally missed that. I added a reset tracker function and hooked it in receive_data to ensure after every file the fd is reset. Added also an edge case for multiple files and another one for batch mode
Thanks for your review!
811708d to
ee67473
Compare
|
@steadytao ubuntu workflow is the only one forced running rsync with --protocol=30 which is failing the tests. |
Fixes #1066
Original feature request by @birdie-github
Currently, rsync --stats reports Literal data, which represents the unmatched file-update data sent over the network. However, network payload is often completely disconnected from the actual disk I/O footprint on the receiving end. Depending on the transfer flags (e.g., default temp-file rebuilding vs. --inplace), a tiny network payload can result in massive disk writes, or vice versa. System administrators currently have no native visibility into the true I/O cost of a sync.
This PR introduces a new metric to the --stats output: Number of modified 4K blocks. It tracks exactly how many 4,096-byte boundaries are written to the receiver's disk, completely decoupling network delta payload from disk I/O footprint.
O(1) Zero-Overhead Implementation
To ensure tracking does not bottleneck the receiver's high-speed I/O loop, the block calculation operates entirely in O(1) time with zero system calls and no loop iterations.
Stateful Tracking: It uses a static state machine (last_tracked_fd and last_touched_blk) to remember the last logical block written. Because the receiver strictly writes sequentially, we can calculate block deltas using purely mathematical boundaries.
Math over Memory: For each write, it calculates the block boundaries using (offset % 4096) and len. It diffs this against the last known state and instantly updates the global counter.
Real-World Examples (Why this matters)
Here is a demonstration of how network data and disk I/O diverge, using a 4MB file where we inject a single byte at 10 different 4K block boundaries:
Case 1: Delta transfer with --inplace
Context: Rsync sends a small amount of literal data over the network to patch the file. Because --inplace is used, the receiver seeks and updates only the 10 specific blocks on disk. Network and Disk I/O are both low.
Case 2: Delta transfer without --inplace (Default temp-file behavior)
Context: Rsync perfectly matches the unchanged data locally. The network payload is effectively zero. However, because rsync reconstructs the file into a new hidden temp file before moving it into place, the receiver's disk is forced to rewrite the entire 4MB file (1,024 blocks).