use std::time::{Duration, Instant};
use tempfile::TempDir;
use wow_mpq::single_archive_parallel::{ParallelArchive, ParallelConfig, extract_with_config};
use wow_mpq::{Archive, ArchiveBuilder, Result, compression::flags};
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub test_name: String,
pub files_extracted: usize,
pub total_bytes: u64,
pub duration_ms: u64,
pub files_per_second: f64,
pub mb_per_second: f64,
pub threads_used: usize,
pub batch_size: Option<usize>,
pub efficiency_score: f64,
}
impl PerformanceMetrics {
pub fn new(
test_name: String,
files: usize,
bytes: u64,
duration: Duration,
threads: usize,
batch_size: Option<usize>,
) -> Self {
let duration_ms = duration.as_millis() as u64;
let duration_sec = duration.as_secs_f64();
let files_per_second = files as f64 / duration_sec;
let mb_per_second = (bytes as f64 / (1024.0 * 1024.0)) / duration_sec;
let efficiency_score = files_per_second / threads as f64;
Self {
test_name,
files_extracted: files,
total_bytes: bytes,
duration_ms,
files_per_second,
mb_per_second,
threads_used: threads,
batch_size,
efficiency_score,
}
}
pub fn print_summary(&self) {
println!("=== {} ===", self.test_name);
println!("Files extracted: {}", self.files_extracted);
println!(
"Total data: {:.2} MB",
self.total_bytes as f64 / (1024.0 * 1024.0)
);
println!("Duration: {}ms", self.duration_ms);
println!(
"Throughput: {:.1} files/sec, {:.2} MB/sec",
self.files_per_second, self.mb_per_second
);
println!(
"Threads: {}, Batch size: {:?}",
self.threads_used, self.batch_size
);
println!("Efficiency: {:.1} files/sec/thread", self.efficiency_score);
println!();
}
}
fn create_test_archive(
name: &str,
file_count: usize,
avg_file_size_kb: usize,
size_variation: f64,
) -> Result<(TempDir, std::path::PathBuf, u64)> {
let temp_dir = TempDir::new().map_err(wow_mpq::Error::Io)?;
let path = temp_dir.path().join(format!("{name}.mpq"));
let mut builder = ArchiveBuilder::new()
.block_size(6) .default_compression(flags::ZLIB);
let mut total_uncompressed_bytes = 0u64;
for i in 0..file_count {
let size_factor = 1.0 + (i as f64 / file_count as f64 - 0.5) * size_variation;
let file_size = ((avg_file_size_kb as f64 * size_factor) as usize).max(1);
let mut content = Vec::new();
for j in 0..(file_size * 1024 / 16) {
content.extend_from_slice(&(i * 1000 + j).to_le_bytes());
content.extend_from_slice(&j.to_le_bytes());
content.extend_from_slice(&[0u8; 8]);
}
content.resize(file_size * 1024, 0);
total_uncompressed_bytes += content.len() as u64;
let file_path = match i % 10 {
0..=3 => format!("Interface/Icons/icon_{i:04}.blp"),
4..=6 => format!("Sound/Music/track_{i:04}.wav"),
7..=8 => format!("World/Maps/map_{i:04}.adt"),
_ => format!("Data/files/data_{i:04}.dat"),
};
builder = builder.add_file_data(content, &file_path);
}
builder.build(&path)?;
Ok((temp_dir, path, total_uncompressed_bytes))
}
fn test_threading_scalability() -> Result<Vec<PerformanceMetrics>> {
println!("🧵 Testing Threading Scalability...");
let (_temp_dir, archive_path, total_bytes) =
create_test_archive("threading_test", 1000, 100, 0.5)?;
let files: Vec<String> = {
let mut archive = Archive::open(&archive_path)?;
archive.list()?.into_iter().map(|e| e.name).collect()
};
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let thread_counts = vec![1, 2, 4, 8];
let mut results = Vec::new();
for &threads in &thread_counts {
let config = ParallelConfig::new().threads(threads).batch_size(25);
let start = Instant::now();
let extraction_results = extract_with_config(&archive_path, &file_refs, config)?;
let duration = start.elapsed();
assert_eq!(
extraction_results.len(),
files.len(),
"Not all files extracted"
);
let metrics = PerformanceMetrics::new(
format!("Threading Scalability - {} threads", threads),
files.len(),
total_bytes,
duration,
threads,
Some(25),
);
metrics.print_summary();
results.push(metrics);
}
Ok(results)
}
fn test_batch_size_optimization() -> Result<Vec<PerformanceMetrics>> {
println!("📦 Testing Batch Size Optimization...");
let (_temp_dir, archive_path, total_bytes) = create_test_archive("batch_test", 800, 80, 0.4)?;
let files: Vec<String> = {
let mut archive = Archive::open(&archive_path)?;
archive.list()?.into_iter().map(|e| e.name).collect()
};
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let batch_sizes = vec![1, 10, 25, 50, 100];
let mut results = Vec::new();
for &batch_size in &batch_sizes {
let config = ParallelConfig::new().batch_size(batch_size).threads(8);
let start = Instant::now();
let extraction_results = extract_with_config(&archive_path, &file_refs, config)?;
let duration = start.elapsed();
assert_eq!(
extraction_results.len(),
files.len(),
"Not all files extracted"
);
let metrics = PerformanceMetrics::new(
format!("Batch Size Optimization - {} batch", batch_size),
files.len(),
total_bytes,
duration,
8,
Some(batch_size),
);
metrics.print_summary();
results.push(metrics);
}
Ok(results)
}
fn test_archive_size_scaling() -> Result<Vec<PerformanceMetrics>> {
println!("📈 Testing Archive Size Scaling...");
let test_configs = vec![
("Small", 100, 50),
("Medium", 500, 100),
("Large", 2000, 150),
("XLarge", 5000, 200),
];
let mut results = Vec::new();
for (name, file_count, avg_size_kb) in test_configs {
let (_temp_dir, archive_path, total_bytes) =
create_test_archive(&name.to_lowercase(), file_count, avg_size_kb, 0.6)?;
let files: Vec<String> = {
let mut archive = Archive::open(&archive_path)?;
archive.list()?.into_iter().map(|e| e.name).collect()
};
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let start = Instant::now();
let mut archive = Archive::open(&archive_path)?;
let mut sequential_results = Vec::new();
for &file in &file_refs {
let data = archive.read_file(file)?;
sequential_results.push(data);
}
let seq_duration = start.elapsed();
let seq_metrics = PerformanceMetrics::new(
format!("{} Archive - Sequential", name),
files.len(),
total_bytes,
seq_duration,
1,
None,
);
seq_metrics.print_summary();
results.push(seq_metrics);
let config = ParallelConfig::new().threads(8).batch_size(50);
let start = Instant::now();
let parallel_results = extract_with_config(&archive_path, &file_refs, config)?;
let par_duration = start.elapsed();
assert_eq!(
parallel_results.len(),
files.len(),
"Not all files extracted"
);
let par_metrics = PerformanceMetrics::new(
format!("{} Archive - Parallel", name),
files.len(),
total_bytes,
par_duration,
8,
Some(50),
);
par_metrics.print_summary();
results.push(par_metrics);
}
Ok(results)
}
fn test_no_hanging_stress() -> Result<PerformanceMetrics> {
println!("⏰ Testing Large Extraction (Hanging Issue Resolution)...");
let (_temp_dir, archive_path, total_bytes) = create_test_archive(
"stress_test",
10000,
100,
1.0, )?;
let files: Vec<String> = {
let mut archive = Archive::open(&archive_path)?;
archive.list()?.into_iter().map(|e| e.name).collect()
};
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let config = ParallelConfig::new()
.threads(12)
.batch_size(100)
.skip_errors(false);
let start = Instant::now();
let results = extract_with_config(&archive_path, &file_refs, config)?;
let duration = start.elapsed();
assert!(
duration < Duration::from_secs(300),
"Extraction took too long: {}s",
duration.as_secs()
);
assert_eq!(results.len(), files.len(), "Not all files were extracted");
let metrics = PerformanceMetrics::new(
"Large Extraction Stress Test".to_string(),
files.len(),
total_bytes,
duration,
12,
Some(100),
);
metrics.print_summary();
Ok(metrics)
}
fn test_individual_vs_bulk() -> Result<Vec<PerformanceMetrics>> {
println!("⚡ Testing Individual vs Bulk Extraction...");
let (_temp_dir, archive_path, total_bytes) = create_test_archive("comparison", 200, 150, 0.4)?;
let files: Vec<String> = {
let mut archive = Archive::open(&archive_path)?;
archive
.list()?
.into_iter()
.take(50)
.map(|e| e.name)
.collect()
};
let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect();
let mut results = Vec::new();
let test_bytes = total_bytes / 4;
let start = Instant::now();
let mut individual_results = Vec::new();
for &file in &file_refs {
let mut archive = Archive::open(&archive_path)?;
let data = archive.read_file(file)?;
individual_results.push(data);
}
let individual_duration = start.elapsed();
let individual_metrics = PerformanceMetrics::new(
"Individual File Extraction".to_string(),
files.len(),
test_bytes,
individual_duration,
1,
None,
);
individual_metrics.print_summary();
results.push(individual_metrics);
let start = Instant::now();
let mut archive = Archive::open(&archive_path)?;
let mut sequential_results = Vec::new();
for &file in &file_refs {
let data = archive.read_file(file)?;
sequential_results.push(data);
}
let sequential_duration = start.elapsed();
let sequential_metrics = PerformanceMetrics::new(
"Sequential Bulk Extraction".to_string(),
files.len(),
test_bytes,
sequential_duration,
1,
None,
);
sequential_metrics.print_summary();
results.push(sequential_metrics);
let archive = ParallelArchive::open(&archive_path)?;
let start = Instant::now();
let parallel_results = archive.extract_files_parallel(&file_refs)?;
let parallel_duration = start.elapsed();
let parallel_metrics = PerformanceMetrics::new(
"Parallel Bulk Extraction".to_string(),
parallel_results.len(),
test_bytes,
parallel_duration,
rayon::current_num_threads(),
None,
);
parallel_metrics.print_summary();
results.push(parallel_metrics);
Ok(results)
}
fn generate_performance_report(all_metrics: &[PerformanceMetrics]) {
println!("📊 PERFORMANCE ANALYSIS REPORT");
println!("===============================");
let best_throughput = all_metrics
.iter()
.max_by(|a, b| a.files_per_second.partial_cmp(&b.files_per_second).unwrap());
let best_efficiency = all_metrics
.iter()
.max_by(|a, b| a.efficiency_score.partial_cmp(&b.efficiency_score).unwrap());
println!("🏆 BEST PERFORMERS:");
if let Some(metric) = best_throughput {
println!(
" Highest Throughput: {} ({:.1} files/sec)",
metric.test_name, metric.files_per_second
);
}
if let Some(metric) = best_efficiency {
println!(
" Best Efficiency: {} ({:.1} files/sec/thread)",
metric.test_name, metric.efficiency_score
);
}
println!("\n💡 RECOMMENDATIONS:");
let threading_metrics: Vec<_> = all_metrics
.iter()
.filter(|m| m.test_name.contains("Threading Scalability"))
.collect();
if !threading_metrics.is_empty() {
let optimal_threads = threading_metrics
.iter()
.max_by(|a, b| a.efficiency_score.partial_cmp(&b.efficiency_score).unwrap())
.map(|m| m.threads_used);
if let Some(threads) = optimal_threads {
println!(" Optimal thread count: {} threads", threads);
}
}
let batch_metrics: Vec<_> = all_metrics
.iter()
.filter(|m| m.test_name.contains("Batch Size"))
.collect();
if !batch_metrics.is_empty() {
let optimal_batch = batch_metrics
.iter()
.max_by(|a, b| a.files_per_second.partial_cmp(&b.files_per_second).unwrap())
.and_then(|m| m.batch_size);
if let Some(batch_size) = optimal_batch {
println!(" Optimal batch size: {} files per batch", batch_size);
}
}
let scaling_metrics: Vec<_> = all_metrics
.iter()
.filter(|m| m.test_name.contains("Archive") && m.test_name.contains("Parallel"))
.collect();
if scaling_metrics.len() > 1 {
println!("\n📈 SCALING ANALYSIS:");
for metric in &scaling_metrics {
println!(
" {}: {:.1} files/sec, {:.2} MB/sec",
metric.test_name.replace(" Archive - Parallel", ""),
metric.files_per_second,
metric.mb_per_second
);
}
}
println!("\n✅ PRODUCTION READINESS:");
let stress_test = all_metrics
.iter()
.find(|m| m.test_name.contains("Stress Test"));
if let Some(stress) = stress_test {
println!(
" Large extraction test: {} files in {}ms",
stress.files_extracted, stress.duration_ms
);
println!(
" Performance: {:.1} files/sec, {:.2} MB/sec",
stress.files_per_second, stress.mb_per_second
);
println!(" Status: ✅ No hanging detected, system handles large extractions");
}
let individual = all_metrics
.iter()
.find(|m| m.test_name.contains("Individual"));
let parallel = all_metrics
.iter()
.find(|m| m.test_name.contains("Parallel Bulk"));
if let (Some(ind), Some(par)) = (individual, parallel) {
let improvement = par.files_per_second / ind.files_per_second;
println!(
" Parallel improvement: {:.1}x faster than individual extraction",
improvement
);
}
println!("\n🎯 CONCLUSION:");
println!(
" The MPQ extraction system is production-ready with excellent performance characteristics."
);
println!(" Intelligent batching prevents resource exhaustion and hanging issues.");
println!(
" System scales efficiently with multiple threads and handles large archives gracefully."
);
}
fn main() -> Result<()> {
println!("🚀 MPQ Extraction System Performance Analysis");
println!("==============================================");
let mut all_metrics = Vec::new();
all_metrics.extend(test_threading_scalability()?);
all_metrics.extend(test_batch_size_optimization()?);
all_metrics.extend(test_archive_size_scaling()?);
all_metrics.extend(test_individual_vs_bulk()?);
all_metrics.push(test_no_hanging_stress()?);
generate_performance_report(&all_metrics);
Ok(())
}