use std::collections::HashMap;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use super::formatting::{avg_i64, fmt_time, fmt_tokens, median, pct};
use super::metrics::bench_run_metrics;
pub fn parse_bench_readme_report(root: &Path) -> Result<String, String> {
let repos = [
"vscode",
"excalidraw",
"django",
"tokio",
"okhttp",
"gin",
"alamofire",
];
let include_raced = std::env::var("CG_INCLUDE_RACED").ok().as_deref() == Some("1");
let mut savings: HashMap<&str, Vec<i64>> = HashMap::new();
let mut out = String::new();
writeln!(
out,
"repo n(w/wo) time WITH->WITHOUT tools W->WO tokens W->WO (saved) cost W->WO (saved)"
)
.unwrap();
for repo in repos {
let dir = root.join(repo);
let run_dirs = if dir.exists() {
fs::read_dir(&dir)
.map_err(|err| format!("failed to read {}: {err}", dir.display()))?
.filter_map(Result::ok)
.filter(|entry| {
entry.file_type().map(|kind| kind.is_dir()).unwrap_or(false)
&& entry.file_name().to_string_lossy().starts_with("run")
})
.map(|entry| entry.path())
.collect::<Vec<_>>()
} else {
Vec::new()
};
let mut with_runs = Vec::new();
let mut without_runs = Vec::new();
let mut raced_excluded = 0usize;
for run_dir in run_dirs {
if let Some(run) = bench_run_metrics(&run_dir.join("run-headless-with.jsonl"))? {
if run.raced && !include_raced {
raced_excluded += 1;
} else {
with_runs.push(run);
}
}
if let Some(run) = bench_run_metrics(&run_dir.join("run-headless-without.jsonl"))? {
without_runs.push(run);
}
}
if with_runs.is_empty() || without_runs.is_empty() {
writeln!(
out,
"{:<11} (incomplete: w={} wo={})",
repo,
with_runs.len(),
without_runs.len()
)
.unwrap();
continue;
}
let w_time = median(with_runs.iter().map(|run| run.duration).collect());
let wo_time = median(without_runs.iter().map(|run| run.duration).collect());
let w_tokens = median(with_runs.iter().map(|run| run.tokens as f64).collect());
let wo_tokens = median(without_runs.iter().map(|run| run.tokens as f64).collect());
let w_cost = median(with_runs.iter().map(|run| run.cost).collect());
let wo_cost = median(without_runs.iter().map(|run| run.cost).collect());
let w_tools = median(with_runs.iter().map(|run| run.tools as f64).collect());
let wo_tools = median(without_runs.iter().map(|run| run.tools as f64).collect());
for (key, value) in [
("time", pct(w_time, wo_time)),
("tokens", pct(w_tokens, wo_tokens)),
("cost", pct(w_cost, wo_cost)),
("tools", pct(w_tools, wo_tools)),
] {
savings.entry(key).or_default().push(value);
}
writeln!(
out,
"{:<11} {}/{} {:<22}{:<12}{:<24}${:.2}->${:.2} ({}%){}",
repo,
with_runs.len(),
without_runs.len(),
format!("{}->{}", fmt_time(w_time), fmt_time(wo_time)),
format!("{}->{}", w_tools.round() as i64, wo_tools.round() as i64),
format!(
"{}->{} ({}%)",
fmt_tokens(w_tokens),
fmt_tokens(wo_tokens),
pct(w_tokens, wo_tokens)
),
w_cost,
wo_cost,
pct(w_cost, wo_cost),
if raced_excluded > 0 {
format!(
" [{raced_excluded} raced run{} excluded]",
if raced_excluded == 1 { "" } else { "s" }
)
} else {
String::new()
}
)
.unwrap();
}
writeln!(
out,
"\nAVERAGE saved: cost {}% · tokens {}% · time {}% · tool calls {}%",
avg_i64(savings.get("cost")),
avg_i64(savings.get("tokens")),
avg_i64(savings.get("time")),
avg_i64(savings.get("tools"))
)
.unwrap();
Ok(out)
}