Files
brk/crates/brk_bencher_visualizer/src/lib.rs
2026-03-04 17:10:15 +01:00

263 lines
8.3 KiB
Rust

mod chart;
mod data;
mod format;
use data::{Cutoffs, DualRun, Result, Run, read_dual_runs, read_runs};
use std::{
fs,
path::{Path, PathBuf},
};
pub struct Visualizer {
workspace_root: PathBuf,
}
impl Visualizer {
pub fn new(workspace_root: impl AsRef<Path>) -> Self {
Self {
workspace_root: workspace_root.as_ref().to_path_buf(),
}
}
pub fn from_cargo_env() -> Result<Self> {
let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.ok_or("Failed to find workspace root")?
.to_path_buf();
Ok(Self { workspace_root })
}
pub fn generate_all_charts(&self) -> Result<()> {
let benches_dir = self.workspace_root.join("benches");
if !benches_dir.exists() {
return Err("Benches directory does not exist".into());
}
for entry in fs::read_dir(&benches_dir)? {
let path = entry?.path();
if path.is_dir() {
let crate_name = path
.file_name()
.and_then(|n| n.to_str())
.ok_or("Invalid crate name")?;
println!("Generating charts for crate: {}", crate_name);
self.generate_crate_charts(&path, crate_name)?;
}
}
Ok(())
}
fn generate_crate_charts(&self, crate_path: &Path, crate_name: &str) -> Result<()> {
let disk_runs = read_runs(crate_path, "disk.csv")?;
let memory_runs = read_dual_runs(crate_path, "memory.csv")?;
let progress_runs = read_runs(crate_path, "progress.csv")?;
let io_runs = read_dual_runs(crate_path, "io.csv")?;
// Combined charts (all runs)
self.generate_combined_charts(
crate_path,
crate_name,
&disk_runs,
&memory_runs,
&progress_runs,
&io_runs,
)?;
// Individual charts (one per run)
self.generate_individual_charts(
crate_path,
crate_name,
&disk_runs,
&memory_runs,
&progress_runs,
&io_runs,
)?;
Ok(())
}
fn generate_combined_charts(
&self,
crate_path: &Path,
crate_name: &str,
disk_runs: &[Run],
memory_runs: &[DualRun],
progress_runs: &[Run],
io_runs: &[DualRun],
) -> Result<()> {
let cutoffs = Cutoffs::from_progress(progress_runs);
// Trim data to per-run cutoffs for fair comparison
let disk_trimmed = cutoffs.trim_runs(disk_runs);
let memory_trimmed = cutoffs.trim_dual_runs(memory_runs);
let io_trimmed = cutoffs.trim_dual_runs(io_runs);
if !disk_trimmed.is_empty() {
chart::generate(
chart::ChartConfig {
output_path: &crate_path.join("disk.svg"),
title: format!("{} — Disk Usage", crate_name),
y_label: "Disk Usage".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
&disk_trimmed,
)?;
}
if !memory_trimmed.is_empty() {
chart::generate_dual(
chart::ChartConfig {
output_path: &crate_path.join("memory.svg"),
title: format!("{} — Memory", crate_name),
y_label: "Memory".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
&memory_trimmed,
"(current)",
"(peak)",
)?;
}
if !progress_runs.is_empty() {
let progress_trimmed = cutoffs.trim_runs(progress_runs);
chart::generate(
chart::ChartConfig {
output_path: &crate_path.join("progress.svg"),
title: format!("{} — Progress", crate_name),
y_label: "Progress".to_string(),
y_format: chart::YAxisFormat::Number,
},
&progress_trimmed,
)?;
}
if !io_trimmed.is_empty() {
// I/O Read (primary column)
let io_read: Vec<_> = io_trimmed
.iter()
.map(|r| Run {
id: r.id.clone(),
data: r.primary.clone(),
})
.collect();
chart::generate(
chart::ChartConfig {
output_path: &crate_path.join("io_read.svg"),
title: format!("{} — I/O Read", crate_name),
y_label: "Bytes Read".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
&io_read,
)?;
// I/O Write (secondary column)
let io_write: Vec<_> = io_trimmed
.iter()
.map(|r| Run {
id: r.id.clone(),
data: r.secondary.clone(),
})
.collect();
chart::generate(
chart::ChartConfig {
output_path: &crate_path.join("io_write.svg"),
title: format!("{} — I/O Write", crate_name),
y_label: "Bytes Written".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
&io_write,
)?;
}
Ok(())
}
fn generate_individual_charts(
&self,
crate_path: &Path,
crate_name: &str,
disk_runs: &[Run],
memory_runs: &[DualRun],
progress_runs: &[Run],
io_runs: &[DualRun],
) -> Result<()> {
for run in disk_runs {
let run_path = crate_path.join(&run.id);
chart::generate(
chart::ChartConfig {
output_path: &run_path.join("disk.svg"),
title: format!("{} — Disk Usage", crate_name),
y_label: "Disk Usage".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
std::slice::from_ref(run),
)?;
}
for run in memory_runs {
let run_path = crate_path.join(&run.id);
chart::generate_dual(
chart::ChartConfig {
output_path: &run_path.join("memory.svg"),
title: format!("{} — Memory", crate_name),
y_label: "Memory".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
std::slice::from_ref(run),
"(current)",
"(peak)",
)?;
}
for run in progress_runs {
let run_path = crate_path.join(&run.id);
chart::generate(
chart::ChartConfig {
output_path: &run_path.join("progress.svg"),
title: format!("{} — Progress", crate_name),
y_label: "Progress".to_string(),
y_format: chart::YAxisFormat::Number,
},
std::slice::from_ref(run),
)?;
}
for run in io_runs {
let run_path = crate_path.join(&run.id);
let read_run = Run {
id: run.id.clone(),
data: run.primary.clone(),
};
chart::generate(
chart::ChartConfig {
output_path: &run_path.join("io_read.svg"),
title: format!("{} — I/O Read", crate_name),
y_label: "Bytes Read".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
std::slice::from_ref(&read_run),
)?;
let write_run = Run {
id: run.id.clone(),
data: run.secondary.clone(),
};
chart::generate(
chart::ChartConfig {
output_path: &run_path.join("io_write.svg"),
title: format!("{} — I/O Write", crate_name),
y_label: "Bytes Written".to_string(),
y_format: chart::YAxisFormat::Bytes,
},
std::slice::from_ref(&write_run),
)?;
}
Ok(())
}
}