use runmat_time::Instant;
use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand, ValueEnum};
use runmat_snapshot::presets::SnapshotPreset;
use runmat_snapshot::{SnapshotBuilder, SnapshotConfig, SnapshotLoader};
#[derive(Parser)]
#[command(name = "runmat-snapshot-tool")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "Create and manage RunMat standard library snapshots")]
#[command(long_about = None)]
struct Cli {
#[arg(short, long)]
verbose: bool,
#[arg(long, value_enum, default_value = "info")]
log_level: LogLevel,
#[arg(short, long, value_enum, default_value = "production")]
preset: PresetName,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Create {
#[arg(short, long)]
output: PathBuf,
#[arg(long)]
compression_level: Option<u32>,
#[arg(long)]
no_compression: bool,
#[arg(long)]
no_validation: bool,
#[arg(long)]
progress: bool,
},
Validate {
input: PathBuf,
#[arg(long)]
strict: bool,
#[arg(long)]
compatibility_only: bool,
},
Info {
input: PathBuf,
#[arg(long)]
detailed: bool,
#[arg(long)]
metrics: bool,
},
Presets {
#[arg(long)]
detailed: bool,
},
Benchmark {
input: PathBuf,
#[arg(short, long, default_value = "10")]
iterations: usize,
#[arg(long, default_value = "3")]
warmup: usize,
},
Compare {
files: Vec<PathBuf>,
#[arg(long)]
size: bool,
#[arg(long)]
performance: bool,
},
}
#[derive(ValueEnum, Clone, Debug)]
enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
#[derive(ValueEnum, Clone, Debug)]
enum PresetName {
Development,
Production,
HighPerformance,
LowMemory,
NetworkOptimized,
Debug,
}
impl From<PresetName> for SnapshotPreset {
fn from(preset: PresetName) -> Self {
match preset {
PresetName::Development => SnapshotPreset::Development,
PresetName::Production => SnapshotPreset::Production,
PresetName::HighPerformance => SnapshotPreset::HighPerformance,
PresetName::LowMemory => SnapshotPreset::LowMemory,
PresetName::NetworkOptimized => SnapshotPreset::NetworkOptimized,
PresetName::Debug => SnapshotPreset::Debug,
}
}
}
fn main() -> Result<()> {
let cli = Cli::parse();
init_logging(cli.log_level, cli.verbose)?;
match cli.command {
Commands::Create {
output,
compression_level,
no_compression,
no_validation,
progress,
} => {
create_snapshot(
cli.preset.into(),
output,
compression_level,
no_compression,
no_validation,
progress,
)?;
}
Commands::Validate {
input,
strict,
compatibility_only,
} => {
validate_snapshot(input, strict, compatibility_only)?;
}
Commands::Info {
input,
detailed,
metrics,
} => {
show_snapshot_info(input, detailed, metrics)?;
}
Commands::Presets { detailed } => {
list_presets(detailed)?;
}
Commands::Benchmark {
input,
iterations,
warmup,
} => {
benchmark_snapshot(input, iterations, warmup)?;
}
Commands::Compare {
files,
size,
performance,
} => {
compare_snapshots(files, size, performance)?;
}
}
Ok(())
}
fn init_logging(level: LogLevel, verbose: bool) -> Result<()> {
use std::io::Write;
let log_level = if verbose {
log::LevelFilter::Debug
} else {
match level {
LogLevel::Error => log::LevelFilter::Error,
LogLevel::Warn => log::LevelFilter::Warn,
LogLevel::Info => log::LevelFilter::Info,
LogLevel::Debug => log::LevelFilter::Debug,
LogLevel::Trace => log::LevelFilter::Trace,
}
};
env_logger::Builder::from_default_env()
.filter_level(log_level)
.format(|buf, record| {
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.3f");
let level = record.level();
let module = record.module_path().unwrap_or("unknown");
match level {
log::Level::Error => writeln!(
buf,
"\x1b[31m[{}] ERROR [{}]: {}\x1b[0m",
timestamp,
module,
record.args()
),
log::Level::Warn => writeln!(
buf,
"\x1b[33m[{}] WARN [{}]: {}\x1b[0m",
timestamp,
module,
record.args()
),
log::Level::Info => writeln!(
buf,
"\x1b[32m[{}] INFO [{}]: {}\x1b[0m",
timestamp,
module,
record.args()
),
log::Level::Debug => writeln!(
buf,
"\x1b[36m[{}] DEBUG [{}]: {}\x1b[0m",
timestamp,
module,
record.args()
),
log::Level::Trace => writeln!(
buf,
"\x1b[35m[{}] TRACE [{}]: {}\x1b[0m",
timestamp,
module,
record.args()
),
}
})
.write_style(env_logger::WriteStyle::Always)
.target(env_logger::Target::Stderr)
.init();
log::info!("Logging initialized at level: {log_level:?}");
Ok(())
}
fn create_snapshot(
preset: SnapshotPreset,
output: PathBuf,
compression_level: Option<u32>,
no_compression: bool,
no_validation: bool,
progress: bool,
) -> Result<()> {
println!("Creating snapshot with preset: {}", preset.name());
let mut config = preset.config();
if let Some(level) = compression_level {
config.compression_level = level.clamp(1, 9);
}
if no_compression {
config.compression_enabled = false;
}
if no_validation {
config.validation_enabled = false;
}
if progress {
config.progress_reporting = true;
}
let start_time = Instant::now();
let builder = SnapshotBuilder::new(config);
builder
.build_and_save(&output)
.with_context(|| format!("Failed to create snapshot at {}", output.display()))?;
let build_time = start_time.elapsed();
let file_size = std::fs::metadata(&output)
.with_context(|| format!("Failed to get metadata for {}", output.display()))?
.len();
println!("✅ Snapshot created successfully!");
println!(" 📁 File: {}", output.display());
println!(" 📏 Size: {}", format_size(file_size));
println!(" ⏱️ Time: {build_time:?}");
let stats = builder.stats();
if !stats.errors.is_empty() {
println!("⚠️ Errors encountered:");
for error in &stats.errors {
println!(" • {error}");
}
}
if !stats.warnings.is_empty() {
println!("⚠️ Warnings:");
for warning in &stats.warnings {
println!(" • {warning}");
}
}
Ok(())
}
fn validate_snapshot(input: PathBuf, strict: bool, compatibility_only: bool) -> Result<()> {
println!("Validating snapshot: {}", input.display());
if !input.exists() {
anyhow::bail!("Snapshot file does not exist: {}", input.display());
}
let start_time = Instant::now();
if compatibility_only {
match SnapshotLoader::get_metadata(&input) {
Ok(metadata) => {
let is_compatible = metadata.is_compatible();
let validation_time = start_time.elapsed();
if is_compatible {
println!("✅ Snapshot is compatible");
} else {
println!("❌ Snapshot compatibility issues detected");
println!(" Current version: {}", env!("CARGO_PKG_VERSION"));
println!(" Snapshot version: {}", metadata.runmat_version);
}
println!(" ⏱️ Validation time: {validation_time:?}");
}
Err(e) => {
println!("❌ Failed to read snapshot metadata: {e}");
return Err(e.into());
}
}
} else {
let config = SnapshotConfig {
validation_enabled: true,
..SnapshotConfig::default()
};
let mut loader = SnapshotLoader::new(config);
match loader.load(&input) {
Ok((_snapshot, stats)) => {
let validation_time = start_time.elapsed();
println!("✅ Snapshot validation passed");
println!(" 📏 Total size: {}", format_size(stats.total_size));
println!(
" 🗜️ Compressed size: {}",
format_size(stats.compressed_size)
);
println!(
" 📊 Compression ratio: {:.1}%",
stats.compression_efficiency() * 100.0
);
println!(" 🔧 Builtins: {}", stats.builtin_count);
println!(" ⏱️ Load time: {:?}", stats.load_time);
println!(" ⏱️ Validation time: {validation_time:?}");
if strict {
println!("🔍 Performing strict validation...");
}
}
Err(e) => {
println!("❌ Snapshot validation failed: {e}");
if strict {
return Err(e.into());
}
}
}
}
Ok(())
}
fn show_snapshot_info(input: PathBuf, detailed: bool, metrics: bool) -> Result<()> {
println!("Snapshot Information: {}", input.display());
println!();
let file_metadata = std::fs::metadata(&input)
.with_context(|| format!("Failed to read file metadata for {}", input.display()))?;
println!("📁 File Information:");
println!(" Size: {}", format_size(file_metadata.len()));
println!(
" Modified: {:?}",
file_metadata.modified().unwrap_or(std::time::UNIX_EPOCH)
);
println!();
match SnapshotLoader::peek_header(&input) {
Ok(header) => {
println!("📋 Snapshot Header:");
println!(" Version: {}", header.version);
println!(" Created: {:?}", header.metadata.created_at);
println!(" RunMat Version: {}", header.metadata.runmat_version);
println!(
" Platform: {} {}",
header.metadata.target_platform.os, header.metadata.target_platform.arch
);
if header.data_info.compression.algorithm
!= runmat_snapshot::format::CompressionAlgorithm::None
{
println!(
" Compression: {:?}",
header.data_info.compression.algorithm
);
println!(
" Compression Ratio: {:.1}%",
(1.0 - header.data_info.compressed_size as f64
/ header.data_info.uncompressed_size as f64)
* 100.0
);
}
if detailed {
println!();
println!("🔧 Build Configuration:");
println!(
" Optimization: {}",
header.metadata.build_config.optimization_level
);
println!(" Debug Info: {}", header.metadata.build_config.debug_info);
println!(" Compiler: {}", header.metadata.build_config.compiler);
println!();
println!("🚀 Features:");
for feature in &header.metadata.feature_flags {
println!(" • {feature}");
}
println!();
println!("🖥️ Platform Details:");
println!(
" CPU Features: {:?}",
header.metadata.target_platform.cpu_features
);
println!(
" Page Size: {}",
header.metadata.target_platform.page_size
);
println!(
" Cache Line Size: {}",
header.metadata.target_platform.cache_line_size
);
}
if metrics {
println!();
println!("📊 Performance Metrics:");
let perf = &header.metadata.performance_metrics;
println!(" Creation Time: {:?}", perf.creation_time);
println!(" Builtin Count: {}", perf.builtin_count);
println!(" HIR Cache Entries: {}", perf.hir_cache_entries);
println!(" Bytecode Cache Entries: {}", perf.bytecode_cache_entries);
println!(
" Peak Memory Usage: {}",
format_size(perf.peak_memory_usage)
);
println!();
println!("⚡ Estimated Load Time: {:?}", header.estimated_load_time());
}
}
Err(e) => {
println!("❌ Failed to read snapshot header: {e}");
return Err(e.into());
}
}
Ok(())
}
fn list_presets(detailed: bool) -> Result<()> {
println!("Available Snapshot Presets:");
println!();
for preset in SnapshotPreset::all_presets() {
println!("🎯 {}", preset.name());
println!(" {}", preset.description());
if detailed {
let chars = preset.characteristics();
println!(" 📊 Characteristics:");
println!(" Build Time: {}", chars.build_time);
println!(" Load Time: {}", chars.load_time);
println!(" File Size: {}", chars.file_size);
println!(" Memory Usage: {}", chars.memory_usage);
println!(" Validation: {}", chars.validation_level);
println!(
" Debug Friendly: {}",
if chars.debugging_friendly {
"Yes"
} else {
"No"
}
);
}
println!();
}
Ok(())
}
fn benchmark_snapshot(input: PathBuf, iterations: usize, warmup: usize) -> Result<()> {
println!("Benchmarking snapshot: {}", input.display());
println!("Iterations: {iterations} (+ {warmup} warmup)");
println!();
let config = SnapshotConfig::default();
let mut loader = SnapshotLoader::new(config);
println!("🔥 Warming up...");
for i in 0..warmup {
print!(" Warmup {}/{}\r", i + 1, warmup);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
loader.load(&input).context("Warmup iteration failed")?;
loader.clear_cache(); }
println!();
println!("📊 Benchmarking...");
let mut load_times = Vec::with_capacity(iterations);
let mut total_stats = None;
for i in 0..iterations {
print!(" Iteration {}/{}\r", i + 1, iterations);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
let start = Instant::now();
let (_, stats) = loader.load(&input).context("Benchmark iteration failed")?;
let total_time = start.elapsed();
load_times.push(total_time);
total_stats = Some(stats);
loader.clear_cache(); }
println!();
load_times.sort();
let min_time = load_times[0];
let max_time = load_times[iterations - 1];
let median_time = load_times[iterations / 2];
let avg_time: std::time::Duration =
load_times.iter().sum::<std::time::Duration>() / iterations as u32;
println!("📈 Benchmark Results:");
println!(" Min Time: {min_time:?}");
println!(" Avg Time: {avg_time:?}");
println!(" Median Time: {median_time:?}");
println!(" Max Time: {max_time:?}");
if let Some(stats) = total_stats {
println!();
println!("📊 Load Statistics:");
println!(" Total Size: {}", format_size(stats.total_size));
println!(
" Throughput: {:.1} MB/s",
stats.loading_throughput() / 1_000_000.0
);
println!(
" Compression Ratio: {:.1}%",
stats.compression_efficiency() * 100.0
);
println!(" Builtins: {}", stats.builtin_count);
}
Ok(())
}
fn compare_snapshots(files: Vec<PathBuf>, show_size: bool, show_performance: bool) -> Result<()> {
if files.len() < 2 {
anyhow::bail!("Need at least 2 files to compare");
}
println!("Comparing {} snapshots:", files.len());
println!();
let mut results = Vec::new();
for file in &files {
if !file.exists() {
println!("❌ File not found: {}", file.display());
continue;
}
match SnapshotLoader::peek_header(file) {
Ok(header) => {
let file_size = std::fs::metadata(file)?.len();
results.push((file.clone(), header, file_size));
}
Err(e) => {
println!("❌ Failed to read {}: {}", file.display(), e);
}
}
}
if results.is_empty() {
anyhow::bail!("No valid snapshots found");
}
println!("📋 Basic Comparison:");
for (file, header, file_size) in &results {
println!(" {}", file.file_name().unwrap().to_string_lossy());
println!(" Version: {}", header.metadata.runmat_version);
println!(" Size: {}", format_size(*file_size));
println!(" Created: {:?}", header.metadata.created_at);
println!();
}
if show_size {
println!("📏 Size Comparison:");
let mut sorted_results = results.clone();
sorted_results.sort_by_key(|(_, _, size)| *size);
for (file, header, file_size) in &sorted_results {
let compression_ratio = if header.data_info.compressed_size > 0 {
(1.0 - header.data_info.compressed_size as f64
/ header.data_info.uncompressed_size as f64)
* 100.0
} else {
0.0
};
println!(
" {} - {} (compression: {:.1}%)",
file.file_name().unwrap().to_string_lossy(),
format_size(*file_size),
compression_ratio
);
}
println!();
}
if show_performance {
println!("⚡ Performance Comparison:");
for (file, header, _) in &results {
let estimated_load = header.estimated_load_time();
let builtin_count = header.metadata.performance_metrics.builtin_count;
println!(
" {} - Est. load: {:?}, Builtins: {}",
file.file_name().unwrap().to_string_lossy(),
estimated_load,
builtin_count
);
}
println!();
}
Ok(())
}
fn format_size(size: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = size as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_size() {
assert_eq!(format_size(512), "512 B");
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1536), "1.5 KB");
assert_eq!(format_size(1024 * 1024), "1.0 MB");
assert_eq!(format_size(1024 * 1024 * 1024), "1.0 GB");
}
#[test]
fn test_preset_conversion() {
let preset: SnapshotPreset = PresetName::Development.into();
assert_eq!(preset.name(), "Development");
let preset: SnapshotPreset = PresetName::Production.into();
assert_eq!(preset.name(), "Production");
}
}