use crate::metrics::catalog::FullProfile;
use crate::metrics::export;
use anyhow::Result;
use std::path::{Path, PathBuf};
fn baseline_dir() -> PathBuf {
PathBuf::from(".cgp-baselines")
}
pub fn save_baseline(name: &str, profile: &FullProfile) -> Result<PathBuf> {
let dir = baseline_dir();
std::fs::create_dir_all(&dir)?;
let path = dir.join(format!("{name}.json"));
export::export_json(profile, &path)?;
Ok(path)
}
pub fn load_baseline(name: &str) -> Result<FullProfile> {
let path = baseline_dir().join(format!("{name}.json"));
if !path.exists() {
anyhow::bail!(
"Baseline '{name}' not found at {}.\n \
Available baselines: {}",
path.display(),
list_baselines().join(", ")
);
}
export::load_json(&path)
}
pub fn list_baselines() -> Vec<String> {
let dir = baseline_dir();
if !dir.exists() {
return vec![];
}
std::fs::read_dir(dir)
.ok()
.map(|entries| {
entries
.filter_map(|e| e.ok())
.filter_map(|e| {
let path = e.path();
if path.extension().is_some_and(|ext| ext == "json") {
path.file_stem().and_then(|s| s.to_str()).map(String::from)
} else {
None
}
})
.collect()
})
.unwrap_or_default()
}
pub fn run_baseline(save: Option<&str>, load: Option<&str>) -> Result<()> {
match (save, load) {
(Some(path), None) => {
let profile = export::load_json(Path::new(path))?;
let name = Path::new(path)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("default");
let saved_path = save_baseline(name, &profile)?;
println!("Baseline '{name}' saved to {}", saved_path.display());
}
(None, Some(name)) => {
let profile = load_baseline(name)?;
println!("Baseline '{name}':");
println!(" Time: {:.1} us", profile.timing.wall_clock_time_us);
println!(" TFLOP/s: {:.1}", profile.throughput.tflops);
if let Some(k) = &profile.kernel {
println!(" Kernel: {}", k.name);
}
if let Some(gpu) = &profile.hardware.gpu {
println!(" GPU: {gpu}");
}
if let Some(health) = &profile.system_health {
println!(
" System: {:.0}°C, {:.0}W, {:.0} MHz",
health.gpu_temperature_celsius, health.gpu_power_watts, health.gpu_clock_mhz
);
}
if let Some(vram) = &profile.vram {
println!(
" VRAM: {:.0}/{:.0} MB ({:.1}%)",
vram.vram_used_mb, vram.vram_total_mb, vram.vram_utilization_pct
);
}
println!(" Timestamp: {}", profile.timestamp);
}
(None, None) => {
let baselines = list_baselines();
if baselines.is_empty() {
println!("No baselines saved yet.");
println!(" Save one with: cgp baseline --save <profile.json>");
} else {
println!("Saved baselines:");
for name in &baselines {
let path = baseline_dir().join(format!("{name}.json"));
let size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
println!(" {name:20} ({:.1} KB)", size as f64 / 1024.0);
}
}
}
(Some(_), Some(_)) => {
anyhow::bail!("Specify either --save or --load, not both");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::catalog::*;
#[test]
fn test_save_and_load_baseline() {
let profile = FullProfile {
version: "2.0".to_string(),
timing: TimingMetrics {
wall_clock_time_us: 23.2,
samples: 50,
..Default::default()
},
throughput: ThroughputMetrics {
tflops: 11.6,
..Default::default()
},
..Default::default()
};
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.json");
export::export_json(&profile, &path).unwrap();
let loaded = export::load_json(&path).unwrap();
assert!((loaded.timing.wall_clock_time_us - 23.2).abs() < 0.01);
}
#[test]
fn test_list_baselines_empty() {
let names = list_baselines();
assert!(names.len() < 1000);
}
}