use heroforge_core::sync::quic::{QuicClient, QuicServer};
use heroforge_core::Repository;
use std::env;
use std::path::{Path, PathBuf};
use std::time::Instant;
use tokio::runtime::Runtime;
fn format_bytes(bytes: usize) -> String {
if bytes >= 1_000_000_000 {
format!("{:.2} GB", bytes as f64 / 1_000_000_000.0)
} else if bytes >= 1_000_000 {
format!("{:.2} MB", bytes as f64 / 1_000_000.0)
} else if bytes >= 1_000 {
format!("{:.2} KB", bytes as f64 / 1_000.0)
} else {
format!("{} bytes", bytes)
}
}
fn format_duration(secs: f64) -> String {
if secs >= 60.0 {
format!("{:.1} min", secs / 60.0)
} else if secs >= 1.0 {
format!("{:.2} sec", secs)
} else {
format!("{:.1} ms", secs * 1000.0)
}
}
fn count_artifacts(repo: &Repository) -> usize {
let conn = repo.database().connection();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM blob", [], |row| row.get(0))
.unwrap_or(0);
count as usize
}
fn get_repo_size(path: &Path) -> u64 {
std::fs::metadata(path).map(|m| m.len()).unwrap_or(0)
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <source.forge>", args[0]);
eprintln!("\nExample:");
eprintln!(" cargo run --example quic_sync_benchmark --features sync-quic --release -- /tmp/heroforge-scm.forge");
std::process::exit(1);
}
let source_path = PathBuf::from(&args[1]);
if !source_path.exists() {
eprintln!("Error: Source file not found: {}", source_path.display());
std::process::exit(1);
}
println!("╔════════════════════════════════════════════════════════════╗");
println!("║ QUIC Sync Benchmark - heroforge ║");
println!("╚════════════════════════════════════════════════════════════╝");
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let dest_path = temp_dir.path().join("dest.forge");
let source_repo = Repository::open(&source_path).expect("Failed to open source repo");
let source_artifacts = count_artifacts(&source_repo);
let source_size = get_repo_size(&source_path);
println!("\nSource repository:");
println!(" Path: {}", source_path.display());
println!(" Size: {}", format_bytes(source_size as usize));
println!(" Artifacts: {}", source_artifacts);
let dest_repo = Repository::init(&dest_path).expect("Failed to create dest repo");
let project_code = source_repo
.project_code()
.expect("Failed to get project code");
dest_repo
.database()
.connection()
.execute(
"UPDATE config SET value = ?1 WHERE name = 'project-code'",
[&project_code],
)
.expect("Failed to set project code");
let dest_artifacts_before = count_artifacts(&dest_repo);
println!("\nDestination repository:");
println!(" Path: {}", dest_path.display());
println!(" Artifacts before: {}", dest_artifacts_before);
println!("\nSync options:");
println!(" Protocol: QUIC (with built-in TLS 1.3)");
println!(" Compression: LZ4");
let rt = Runtime::new().expect("Failed to create runtime");
let (addr_tx, addr_rx) = std::sync::mpsc::channel();
let source_path_clone = source_path.clone();
let dest_path_clone = dest_path.clone();
println!("\nStarting server...");
let server_handle = std::thread::spawn(move || {
let rt = Runtime::new().expect("runtime");
rt.block_on(async {
let server = QuicServer::bind("127.0.0.1:0").expect("Failed to bind server");
let addr = server.local_addr().expect("Failed to get addr").to_string();
println!(" Server listening on {}", addr);
addr_tx.send(addr).expect("Failed to send addr");
let repo = Repository::open(&source_path_clone).expect("open source");
println!(" Server ready, handling sync...");
let stats = server
.handle_sync(&repo, &source_path_clone)
.await
.expect("sync failed");
println!(" Server: sync complete!");
stats
})
});
let addr = addr_rx.recv().expect("Failed to receive server address");
println!("\nSyncing {} artifacts...", source_artifacts);
let start = Instant::now();
let client_stats = rt.block_on(async {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
let repo = Repository::open_rw(&dest_path_clone).expect("open dest");
QuicClient::sync(&repo, &dest_path_clone, &addr)
.await
.expect("sync failed")
});
let elapsed = start.elapsed();
let elapsed_secs = elapsed.as_secs_f64();
let server_stats = server_handle.join().expect("Server thread panicked");
let dest_repo = Repository::open(&dest_path).expect("Failed to reopen dest");
let dest_artifacts_after = count_artifacts(&dest_repo);
let dest_size = get_repo_size(&dest_path);
let artifacts_synced = dest_artifacts_after - dest_artifacts_before;
let bytes_transferred = client_stats.bytes_received + client_stats.bytes_sent;
let throughput = if elapsed_secs > 0.0 {
bytes_transferred as f64 / elapsed_secs
} else {
0.0
};
println!("\n{}", "=".repeat(60));
println!(" RESULTS");
println!("{}", "=".repeat(60));
println!("Time: {}", format_duration(elapsed_secs));
println!("Artifacts synced: {}", artifacts_synced);
println!("Bytes transferred: {}", format_bytes(bytes_transferred));
println!("Throughput: {}/sec", format_bytes(throughput as usize));
if source_size > 0 {
let ratio = source_size as f64 / bytes_transferred as f64;
println!("Compression ratio: {:.1}x", ratio);
}
println!("\nClient stats:");
println!(" Sent: {}", format_bytes(client_stats.bytes_sent));
println!(" Received: {}", format_bytes(client_stats.bytes_received));
println!(" Artifacts sent: {}", client_stats.artifacts_sent);
println!(" Artifacts received: {}", client_stats.artifacts_received);
println!("\nServer stats:");
println!(" Sent: {}", format_bytes(server_stats.bytes_sent));
println!(" Received: {}", format_bytes(server_stats.bytes_received));
println!("\nDestination repository after sync:");
println!(" Artifacts: {}", dest_artifacts_after);
println!(" Size: {}", format_bytes(dest_size as usize));
if dest_artifacts_after == source_artifacts {
println!(
"\n✓ Verification passed: all {} artifacts synced correctly",
source_artifacts
);
} else {
println!(
"\n✗ Verification failed: expected {} artifacts, got {}",
source_artifacts, dest_artifacts_after
);
}
}