use std::fmt::Write as _;
use crate::Record;
const MIB: f64 = 1048576.0;
pub fn markdown(label: &str, records: &[Record]) -> String {
let mut out = String::new();
writeln!(out, "# knot-static scale bench: {label}\n").unwrap();
writeln!(
out,
"wall and cpu (user+sys) and peak rss (one process's high-water mark, mapped pack pages included) are the knot-static child's, with the git and \
nginx processes it reaped (a write is stopped once it answered, see answered). written is what the operation created under the knot's state and output roots, disk what they hold after it. \
export, smart and fsck are knot-static's own phase timings; atproto is the build's \
wall left over after its reported phases (cob state, chain settle, atproto and blob \
files). anon and file are the most anonymous and file-backed memory the child's \
process tree held at once, summed over its processes and sampled every 50 ms. a generation's bytes are split by the previous generation's file at the same \
path: shared (same inode), identical (new inode, same bytes), changed, new.\n"
)
.unwrap();
let mut ops: Vec<&str> = Vec::new();
for record in records {
if record.op != "aborted" && !ops.contains(&record.op.as_str()) {
ops.push(&record.op);
}
}
for op in ops {
writeln!(out, "## {op}\n").unwrap();
writeln!(
out,
"| axis | n | wall s | answered s | cpu s | rss MiB | anon MiB | file MiB | written MiB | files | export s | smart ms | fsck ms | atproto s | gen MiB | shared | identical | changed | new | conf KiB | nginx -t s | reload s | gens | disk MiB |"
)
.unwrap();
writeln!(out, "|{}", "---|".repeat(24)).unwrap();
for record in records.iter().filter(|record| record.op == op) {
row(&mut out, record);
}
writeln!(out).unwrap();
}
aborted(&mut out, records);
smart(&mut out, records);
rewritten(&mut out, records);
out
}
fn aborted(out: &mut String, records: &[Record]) {
let stopped: Vec<&Record> = records.iter().filter(|r| r.op == "aborted").collect();
if stopped.is_empty() {
return;
}
writeln!(
out,
"## points that stopped early
"
)
.unwrap();
for record in stopped {
let error = record.error.as_deref().unwrap_or_default();
let error: String = error.lines().take(4).collect::<Vec<_>>().join(" / ");
writeln!(out, "- {} {}: {}", record.axis, record.value, error).unwrap();
}
writeln!(out).unwrap();
}
fn row(out: &mut String, record: &Record) {
let usage = &record.usage;
let build = &record.phases.build_s;
let trace = &record.phases.trace_ms;
let phase = |key: &str| build.get(key).copied();
let traced = |prefix: &str| {
let sum: f64 = trace
.iter()
.filter(|(label, _)| label.starts_with(prefix))
.map(|(_, ms)| ms)
.sum();
(sum > 0.0).then_some(sum)
};
let atproto = phase("total_wall_seconds").map(|total| {
total
- [
"planning_seconds",
"git_export_seconds",
"staging_seconds",
"publication_seconds",
"releases_seconds",
]
.iter()
.filter_map(|key| phase(key))
.sum::<f64>()
});
let generation = record.generation.as_ref();
let sharing = generation.and_then(|generation| generation.sharing.as_ref());
let kind = |pick: fn(&crate::disk::Kinds) -> u64| sharing.map(|s| pick(&s.bytes) as f64 / MIB);
let status = if record.ok { "" } else { " (failed)" };
let cells = [
format!("{}{status}", record.axis),
record.value.to_string(),
fixed(Some(usage.wall_s), 3),
fixed(usage.answered_s, 3),
fixed(Some(usage.user_s + usage.sys_s), 3),
fixed(Some(usage.max_rss_bytes as f64 / MIB), 1),
fixed(usage.peak_anon_bytes.map(|b| b as f64 / MIB), 1),
fixed(usage.peak_file_bytes.map(|b| b as f64 / MIB), 1),
fixed(Some(record.written.bytes as f64 / MIB), 2),
record.written.files.to_string(),
fixed(phase("git_export_seconds"), 3),
fixed(traced("export: smart"), 0),
fixed(traced("export: fsck"), 0),
fixed(atproto, 3),
fixed(generation.map(|g| g.disk_bytes as f64 / MIB), 2),
fixed(kind(|k| k.shared), 2),
fixed(kind(|k| k.identical), 2),
fixed(kind(|k| k.changed), 2),
fixed(kind(|k| k.new), 2),
fixed(generation.map(|g| g.nginx_conf_bytes as f64 / 1024.0), 0),
fixed(record.activation["config_test_seconds"].as_f64(), 3),
fixed(record.reload_s, 3),
record.generations_on_disk.to_string(),
fixed(Some(record.disk_bytes as f64 / MIB), 1),
];
writeln!(out, "| {} |", cells.join(" | ")).unwrap();
}
fn fixed(value: Option<f64>, places: usize) -> String {
value.map_or_else(String::new, |value| format!("{value:.places$}"))
}
fn smart(out: &mut String, records: &[Record]) {
writeln!(out, "## canned fetch replies (KiB)\n").unwrap();
writeln!(
out,
"| axis | n | op | upload-pack | fetch-done | shallow | shallow-head | bases | have-N-done total | have-N total |"
)
.unwrap();
writeln!(out, "|{}", "---|".repeat(10)).unwrap();
for record in records {
let Some(generation) = &record.generation else {
continue;
};
let smart = &generation.smart;
let kib = |name: &str| fixed(smart.get(name).map(|bytes| *bytes as f64 / 1024.0), 1);
let sum = |done: bool| {
let total: u64 = smart
.iter()
.filter(|(name, _)| name.starts_with("have-") && name.ends_with("-done") == done)
.map(|(_, bytes)| bytes)
.sum();
fixed(Some(total as f64 / 1024.0), 1)
};
let bases = smart
.keys()
.filter(|name| name.starts_with("have-") && name.ends_with("-done"))
.count();
writeln!(
out,
"| {} | {} | {} | {} | {} | {} | {} | {bases} | {} | {} |",
record.axis,
record.value,
record.op,
kib("upload-pack"),
kib("fetch-done"),
kib("shallow"),
kib("shallow-head"),
sum(true),
sum(false),
)
.unwrap();
}
writeln!(out).unwrap();
}
fn rewritten(out: &mut String, records: &[Record]) {
writeln!(
out,
"## rewritten per generation, by path (KiB; identical/changed/new)\n"
)
.unwrap();
for record in records {
let Some(sharing) = record.generation.as_ref().and_then(|g| g.sharing.as_ref()) else {
continue;
};
let mut groups: Vec<(&String, &crate::disk::Kinds)> = sharing
.by_group
.iter()
.filter(|(_, kinds)| kinds.identical + kinds.changed + kinds.new > 0)
.collect();
groups.sort_by_key(|(_, kinds)| {
std::cmp::Reverse(kinds.identical + kinds.changed + kinds.new)
});
let listed: Vec<String> = groups
.iter()
.take(6)
.map(|(group, kinds)| {
let kib = |bytes: u64| bytes as f64 / 1024.0;
format!(
"`{group}` {:.0}/{:.0}/{:.0}",
kib(kinds.identical),
kib(kinds.changed),
kib(kinds.new)
)
})
.collect();
writeln!(
out,
"- {} {} {}: {}",
record.axis,
record.value,
record.op,
listed.join(", ")
)
.unwrap();
}
}