use heroforge_core::Repository;
use std::path::Path;
use std::time::Instant;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let test_dir = Path::new("/tmp/fossil_perf_test");
if test_dir.exists() {
std::fs::remove_dir_all(test_dir)?;
}
std::fs::create_dir_all(test_dir)?;
println!("=== heroforge Performance Tests ===\n");
let repo_path = test_dir.join("perf.forge");
let start = Instant::now();
let repo = Repository::init(&repo_path)?;
let init_time = start.elapsed();
println!(
"Repository init: {:>8.2} ms",
init_time.as_secs_f64() * 1000.0
);
let start = Instant::now();
let init_hash = repo
.commit_builder()
.message("initial empty check-in")
.author("perf-user")
.initial()
.execute()?;
let initial_time = start.elapsed();
println!(
"Initial check-in: {:>8.2} ms",
initial_time.as_secs_f64() * 1000.0
);
let num_commits = 100;
let start = Instant::now();
let mut parent = init_hash;
for i in 0..num_commits {
let content = format!("Content version {}\n", i);
parent = repo
.commit_builder()
.message(&format!("Commit {}", i))
.author("perf-user")
.parent(&parent)
.file("file.txt", content.as_bytes())
.execute()?;
}
let commit_time = start.elapsed();
println!(
"{} single-file commits: {:>8.2} ms ({:.2} ms/commit)",
num_commits,
commit_time.as_secs_f64() * 1000.0,
commit_time.as_secs_f64() * 1000.0 / num_commits as f64
);
let num_files = 50;
let mut files_data: Vec<(String, Vec<u8>)> = Vec::new();
for i in 0..num_files {
let name = format!("src/file_{:03}.rs", i);
let content = format!("// File {}\npub fn func_{}() -> i32 {{ {} }}\n", i, i, i);
files_data.push((name, content.into_bytes()));
}
let files: Vec<(&str, &[u8])> = files_data
.iter()
.map(|(n, c)| (n.as_str(), c.as_slice()))
.collect();
let start = Instant::now();
let multi_hash = repo
.commit_builder()
.message("Add 50 files")
.author("perf-user")
.parent(&parent)
.files(&files)
.execute()?;
let multi_time = start.elapsed();
println!(
"{}-file commit: {:>8.2} ms ({:.2} ms/file)",
num_files,
multi_time.as_secs_f64() * 1000.0,
multi_time.as_secs_f64() * 1000.0 / num_files as f64
);
let start = Instant::now();
let file_list = repo.files().at_commit(&multi_hash).list()?;
let list_time = start.elapsed();
println!(
"List {} files: {:>8.2} ms",
file_list.len(),
list_time.as_secs_f64() * 1000.0
);
let start = Instant::now();
for file in &file_list {
let _ = repo.files().at_commit(&multi_hash).read(&file.name)?;
}
let read_time = start.elapsed();
println!(
"Read {} files: {:>8.2} ms ({:.2} ms/file)",
file_list.len(),
read_time.as_secs_f64() * 1000.0,
read_time.as_secs_f64() * 1000.0 / file_list.len() as f64
);
let large_size = 1024 * 1024; let large_content: Vec<u8> = (0..large_size).map(|i| (i % 256) as u8).collect();
let start = Instant::now();
let large_hash = repo
.commit_builder()
.message("Add 1MB file")
.author("perf-user")
.parent(&multi_hash)
.file("large_file.bin", &large_content)
.execute()?;
let large_write_time = start.elapsed();
println!(
"Write 1 MB file: {:>8.2} ms ({:.2} MB/s)",
large_write_time.as_secs_f64() * 1000.0,
1.0 / large_write_time.as_secs_f64()
);
let start = Instant::now();
let read_content = repo.files().at_commit(&large_hash).read("large_file.bin")?;
let large_read_time = start.elapsed();
assert_eq!(read_content.len(), large_size);
println!(
"Read 1 MB file: {:>8.2} ms ({:.2} MB/s)",
large_read_time.as_secs_f64() * 1000.0,
1.0 / large_read_time.as_secs_f64()
);
println!("\nPreparing 100,000 files...");
let num_mass_files = 100_000;
let prep_start = Instant::now();
let mut mass_files_data: Vec<(String, Vec<u8>)> = Vec::with_capacity(num_mass_files);
for i in 0..num_mass_files {
let dir_num = i / 100;
let content = format!("File {} in directory {}\nSome content line 2\n", i, dir_num);
let content_bytes = content.into_bytes();
let content_hash = format!("{:x}", md5::compute(&content_bytes));
let name = format!("dir_{:04}/{}.txt", dir_num, content_hash);
mass_files_data.push((name, content_bytes));
}
let prep_time = prep_start.elapsed();
println!(
"Prepared {} files: {:>8.2} ms",
num_mass_files,
prep_time.as_secs_f64() * 1000.0
);
let mass_files: Vec<(&str, &[u8])> = mass_files_data
.iter()
.map(|(n, c)| (n.as_str(), c.as_slice()))
.collect();
println!("Writing {} files to repo...", num_mass_files);
let start = Instant::now();
let mass_hash = repo
.commit_builder()
.message("Add 100,000 files")
.author("perf-user")
.parent(&large_hash)
.files(&mass_files)
.execute()?;
let mass_write_time = start.elapsed();
println!(
"Write {} files: {:>8.2} ms ({:.2} files/sec)",
num_mass_files,
mass_write_time.as_secs_f64() * 1000.0,
num_mass_files as f64 / mass_write_time.as_secs_f64()
);
let start = Instant::now();
let mass_file_list = repo.files().at_commit(&mass_hash).list()?;
let mass_list_time = start.elapsed();
println!(
"List {} files: {:>8.2} ms",
mass_file_list.len(),
mass_list_time.as_secs_f64() * 1000.0
);
println!("\nReading and verifying all {} files...", num_mass_files);
let start = Instant::now();
let mut read_count = 0;
let mut verified_count = 0;
let mut total_bytes = 0usize;
for file in &mass_file_list {
let content = repo.files().at_commit(&mass_hash).read(&file.name)?;
total_bytes += content.len();
read_count += 1;
let content_hash = format!("{:x}", md5::compute(&content));
if file.name.contains(&content_hash) {
verified_count += 1;
}
}
let mass_read_time = start.elapsed();
println!(
"Read {} files: {:>8.2} ms ({:.2} files/sec, {:.2} MB total)",
read_count,
mass_read_time.as_secs_f64() * 1000.0,
read_count as f64 / mass_read_time.as_secs_f64(),
total_bytes as f64 / (1024.0 * 1024.0)
);
println!(
"Verified {} of {} files (MD5 hash matches filename)",
verified_count, read_count
);
let start = Instant::now();
let _branch = repo
.branches()
.create("perf-branch")
.from_commit(&mass_hash)
.author("perf-user")
.execute()?;
let branch_time = start.elapsed();
println!(
"\nCreate branch: {:>8.2} ms",
branch_time.as_secs_f64() * 1000.0
);
let start = Instant::now();
repo.tags()
.create("perf-tag")
.at_commit(&mass_hash)
.author("perf-user")
.execute()?;
let tag_time = start.elapsed();
println!(
"Add tag: {:>8.2} ms",
tag_time.as_secs_f64() * 1000.0
);
let start = Instant::now();
let checkins = repo.history().recent(100)?;
let checkin_time = start.elapsed();
println!(
"List {} check-ins: {:>8.2} ms",
checkins.len(),
checkin_time.as_secs_f64() * 1000.0
);
let start = Instant::now();
let branches = repo.branches().list()?;
let tags = repo.tags().list()?;
let list_bt_time = start.elapsed();
println!(
"List branches/tags: {:>8.2} ms ({} branches, {} tags)",
list_bt_time.as_secs_f64() * 1000.0,
branches.len(),
tags.len()
);
println!("\n=== Summary ===");
let total_commits = num_commits + 4; println!("Total commits created: {}", total_commits);
println!("Total files in repo: {}", mass_file_list.len());
println!(
"Avg commit time: {:.2} ms",
(commit_time.as_secs_f64() * 1000.0) / num_commits as f64
);
println!(
"Avg file read time: {:.4} ms",
(mass_read_time.as_secs_f64() * 1000.0) / read_count as f64
);
println!(
"Mass write rate: {:.0} files/sec",
num_mass_files as f64 / mass_write_time.as_secs_f64()
);
println!(
"Mass read rate: {:.0} files/sec",
read_count as f64 / mass_read_time.as_secs_f64()
);
std::fs::remove_dir_all(test_dir)?;
println!("\n[PASS] All performance tests completed!");
Ok(())
}