Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions src/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ impl BackupWriter {
self.stats.files += 1;
monitor.count(Counter::Files, 1);
let apath = source_entry.apath();
let size = source_entry.size().expect("source entry has a size");
self.stats.source_files_bytes += size;
trace!(?apath, "Copying file");
let result = if let Some(basis_entry) = basis_entry {
if content_heuristically_unchanged(source_entry, basis_entry) {
Expand All @@ -293,6 +295,7 @@ impl BackupWriter {
.all(|addr| self.block_dir.contains(&addr.hash))
{
self.stats.unmodified_files += 1;
self.stats.source_unchanged_files_bytes += size;
let new_entry = IndexEntry {
addrs: basis_entry.addrs.clone(),
..IndexEntry::metadata_from(source_entry)
Expand All @@ -308,19 +311,21 @@ impl BackupWriter {
} else {
warn!(%apath, ?basis_entry.addrs, "Some referenced blocks are missing or truncated; file will be stored again");
self.stats.modified_files += 1;
self.stats.source_modified_files_bytes += size;
self.stats.replaced_damaged_blocks += 1;
Some(EntryChange::changed(basis_entry, source_entry))
}
} else {
self.stats.modified_files += 1;
self.stats.source_modified_files_bytes += size;
Some(EntryChange::changed(basis_entry, source_entry))
}
} else {
self.stats.new_files += 1;
self.stats.source_new_files_bytes += size;
trace!("New file");
Some(EntryChange::added(source_entry))
};
let size = source_entry.size().expect("source entry has a size");
if size == 0 {
self.index_writer
.push_entry(IndexEntry::metadata_from(source_entry));
Expand Down Expand Up @@ -570,7 +575,6 @@ fn content_heuristically_unchanged<E: EntryTrait, O: EntryTrait>(

#[derive(Add, AddAssign, Debug, Default, Eq, PartialEq, Clone)]
pub struct BackupStats {
// TODO: Include source file bytes, including unmodified files.
pub files: usize,
pub symlinks: usize,
pub directories: usize,
Expand All @@ -584,6 +588,15 @@ pub struct BackupStats {
/// some of their blocks were damaged.
pub replaced_damaged_blocks: usize,

/// Total bytes in all source files.
pub source_files_bytes: u64,
/// Bytes in unchanged files.
pub source_unchanged_files_bytes: u64,
/// Bytes in new files.
pub source_new_files_bytes: u64,
/// Bytes in modified files.
pub source_modified_files_bytes: u64,

/// Bytes that matched an existing block.
pub deduplicated_bytes: u64,
/// Bytes that were stored as new blocks, before compression.
Expand Down Expand Up @@ -620,6 +633,12 @@ impl fmt::Display for BackupStats {
write_count(w, "unsupported file kind", self.unknown_kind);
writeln!(w).unwrap();

write_size(w, "source tree size:", self.source_files_bytes);
write_size(w, " unchanged files", self.source_unchanged_files_bytes);
write_size(w, " modified files", self.source_modified_files_bytes);
write_size(w, " new files", self.source_new_files_bytes);
writeln!(w).unwrap();

write_count(w, "files stored:", self.new_files + self.modified_files);
write_count(w, " empty files", self.empty_files);
write_count(w, " small combined files", self.small_combined_files);
Expand Down Expand Up @@ -670,6 +689,43 @@ mod test {

use super::*;

#[tokio::test]
async fn source_tree_size_in_stats() {
let archive = Archive::create_temp().await;
let src = TreeFixture::new();
let monitor = TestMonitor::arc();

// Create files with known sizes
src.create_file_with_contents("file1", b"hello"); // 5 bytes
src.create_file_with_contents("file2", b"world!!!"); // 8 bytes
src.create_file_with_contents("file3", b"testing backup stats feature"); // 28 bytes

let stats = backup(&archive, src.path(), &BackupOptions::default(), monitor.clone())
.await
.unwrap();

// All files are new
assert_eq!(stats.new_files, 3);
assert_eq!(stats.source_files_bytes, 41); // 5 + 8 + 28
assert_eq!(stats.source_new_files_bytes, 41);
assert_eq!(stats.source_modified_files_bytes, 0);
assert_eq!(stats.source_unchanged_files_bytes, 0);

// Second backup - modify one file
src.create_file_with_contents("file2", b"changed content here"); // 20 bytes

let stats2 = backup(&archive, src.path(), &BackupOptions::default(), monitor.clone())
.await
.unwrap();

assert_eq!(stats2.modified_files, 1);
assert_eq!(stats2.unmodified_files, 2);
assert_eq!(stats2.source_files_bytes, 53); // 5 + 20 + 28
assert_eq!(stats2.source_new_files_bytes, 0);
assert_eq!(stats2.source_modified_files_bytes, 20);
assert_eq!(stats2.source_unchanged_files_bytes, 33); // 5 + 28
}

#[tokio::test]
async fn deleted_files_are_reported() {
// tracing_subscriber::fmt::init();
Expand Down