feat(fs): delta savings in sync stats + exclude interrupted-sync temp files
- Add SyncStats.file_bytes: total uncompressed size of transferred files; lets CLI compute CDC delta savings (e.g. "1.2 MB file bytes, 45 KB wire, 96% savings") - Update cmd_sync summary to show "X file bytes (Y wire, Z% delta savings)" when delta savings are non-trivial; falls back to plain "X bytes" for cold copies - Exclude *.tmp.clawsync from FsManifest::build() unconditionally so leftover temp files from interrupted syncs never appear in manifests or get transferred - Add manifest unit test confirming temp file exclusion Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b8365a4d41
commit
e2aa5a96a5
@@ -1215,12 +1215,23 @@ async fn cmd_sync(
|
|||||||
mods.len(),
|
mods.len(),
|
||||||
rems.len()
|
rems.len()
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
let wire = stats.bytes_transferred;
|
||||||
|
let files = stats.file_bytes;
|
||||||
|
if files > 0 && wire < files {
|
||||||
|
let savings_pct = 100u64 - wire * 100 / files;
|
||||||
|
println!(
|
||||||
|
"Sync complete: {} added, {} modified, {} removed, {} file bytes ({} wire, {}% delta savings).",
|
||||||
|
stats.files_added, stats.files_modified, stats.files_removed,
|
||||||
|
files, wire, savings_pct
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Sync complete: {} added, {} modified, {} removed, {} bytes.",
|
"Sync complete: {} added, {} modified, {} removed, {} bytes.",
|
||||||
stats.files_added, stats.files_modified, stats.files_removed, stats.bytes_transferred
|
stats.files_added, stats.files_modified, stats.files_removed, wire
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ impl FsManifest {
|
|||||||
if !rel.is_empty() && excludes.is_match(&rel) {
|
if !rel.is_empty() && excludes.is_match(&rel) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Always exclude clawsync temp files regardless of caller excludes.
|
||||||
|
// These are leftover from interrupted syncs and must never appear
|
||||||
|
// in manifests or be transferred to peers.
|
||||||
|
if rel.ends_with(".tmp.clawsync") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
candidates.push((abs, rel));
|
candidates.push((abs, rel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,4 +198,18 @@ mod tests {
|
|||||||
.collect();
|
.collect();
|
||||||
assert_eq!(paths, vec!["a.bin", "m.bin", "z.bin"]);
|
assert_eq!(paths, vec!["a.bin", "m.bin", "z.bin"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_always_excludes_clawsync_temp_files() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
fs::write(dir.path().join("real.bin"), b"real content").unwrap();
|
||||||
|
// Simulate a leftover temp file from an interrupted sync.
|
||||||
|
fs::write(dir.path().join("real.tmp.clawsync"), b"incomplete").unwrap();
|
||||||
|
fs::write(dir.path().join("other.bin.tmp.clawsync"), b"also incomplete").unwrap();
|
||||||
|
|
||||||
|
let manifest = FsManifest::build(dir.path(), &empty_excludes()).unwrap();
|
||||||
|
|
||||||
|
let paths: Vec<&str> = manifest.entries.iter().map(|e| e.rel_path.as_str()).collect();
|
||||||
|
assert_eq!(paths, vec!["real.bin"], "temp files must be excluded from manifest");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,14 @@ pub struct SyncStats {
|
|||||||
pub files_added: u32,
|
pub files_added: u32,
|
||||||
pub files_modified: u32,
|
pub files_modified: u32,
|
||||||
pub files_removed: u32,
|
pub files_removed: u32,
|
||||||
|
/// Actual bytes sent over the wire (compressed literal chunks only).
|
||||||
pub bytes_transferred: u64,
|
pub bytes_transferred: u64,
|
||||||
|
/// Total uncompressed size of every file that was added or modified.
|
||||||
|
///
|
||||||
|
/// Comparing `file_bytes` to `bytes_transferred` shows the CDC delta
|
||||||
|
/// savings (e.g. 96% of file_bytes skipped because blocks already exist
|
||||||
|
/// on the server). Zero when no files are transferred.
|
||||||
|
pub file_bytes: u64,
|
||||||
/// Populated only in dry-run mode: paths that would be added.
|
/// Populated only in dry-run mode: paths that would be added.
|
||||||
pub would_add: Option<Vec<String>>,
|
pub would_add: Option<Vec<String>>,
|
||||||
/// Populated only in dry-run mode: paths that would be modified.
|
/// Populated only in dry-run mode: paths that would be modified.
|
||||||
@@ -149,6 +156,7 @@ impl FsSyncClient {
|
|||||||
files_modified: would_modify.len() as u32,
|
files_modified: would_modify.len() as u32,
|
||||||
files_removed: would_remove.len() as u32,
|
files_removed: would_remove.len() as u32,
|
||||||
bytes_transferred: 0,
|
bytes_transferred: 0,
|
||||||
|
file_bytes: 0,
|
||||||
would_add: Some(would_add),
|
would_add: Some(would_add),
|
||||||
would_modify: Some(would_modify),
|
would_modify: Some(would_modify),
|
||||||
would_remove: Some(would_remove),
|
would_remove: Some(would_remove),
|
||||||
@@ -197,6 +205,13 @@ impl FsSyncClient {
|
|||||||
// The semaphore (W=16) limits in-flight FsCdcData messages.
|
// The semaphore (W=16) limits in-flight FsCdcData messages.
|
||||||
|
|
||||||
let total = needed_files.len();
|
let total = needed_files.len();
|
||||||
|
// Sum up the uncompressed sizes of files we'll transfer (from the manifest).
|
||||||
|
// This lets us report delta savings alongside the wire byte count.
|
||||||
|
let file_bytes: u64 = needed_files
|
||||||
|
.iter()
|
||||||
|
.filter_map(|f| manifest.get(&f.path))
|
||||||
|
.map(|e| e.size)
|
||||||
|
.sum();
|
||||||
let progress_tx = self.progress_tx.take();
|
let progress_tx = self.progress_tx.take();
|
||||||
if let Some(tx) = &progress_tx {
|
if let Some(tx) = &progress_tx {
|
||||||
let _ = tx.send(ProgressEvent::TotalFiles(total));
|
let _ = tx.send(ProgressEvent::TotalFiles(total));
|
||||||
@@ -297,6 +312,7 @@ impl FsSyncClient {
|
|||||||
files_modified,
|
files_modified,
|
||||||
files_removed,
|
files_removed,
|
||||||
bytes_transferred,
|
bytes_transferred,
|
||||||
|
file_bytes,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
other => {
|
other => {
|
||||||
|
|||||||
Reference in New Issue
Block a user