Skip to main content

sva_engine/cache/
log.rs

1// Concern: writes a render's lookups as log lines, per second of output and per node | Non-concern: counting them (stats.rs), where the lines go or when | IO: (CacheStats, rate) -> text
2
3use std::collections::BTreeMap;
4use std::fmt::Write;
5
6use super::{CacheStats, Lookup, Tally};
7
8fn line(tally: &Tally) -> String {
9    format!(
10        "hit={} miss={} prefix={} new={} reused={}",
11        tally.hit, tally.miss, tally.prefix, tally.new, tally.reused
12    )
13}
14
15fn percent(part: usize, whole: usize) -> f64 {
16    match whole {
17        0 => 0.0,
18        _ => 100.0 * part as f64 / whole as f64,
19    }
20}
21
22/// Read once the render ends, so each lookup is counted as it finally settled: a run is stored
23/// only when its node ends. `pass` is what the render looked up before its first block.
24pub fn cache_log(stats: &CacheStats, rate: u32) -> String {
25    let (lookups, reached) = (&stats.lookups, &stats.reached);
26    let mut out = String::new();
27    let mut cuts: Vec<(String, usize)> = Vec::new();
28    let mut second = None;
29    for (k, &(at, made)) in reached.iter().enumerate() {
30        let whole = at.div_euclid(i64::from(rate));
31        match second {
32            None => cuts.push(("pass".to_string(), made)),
33            Some(last) if whole > last || k + 1 == reached.len() => {
34                cuts.push((format!("t={:.3}s", at as f64 / f64::from(rate)), made));
35            }
36            Some(_) => continue,
37        }
38        second = Some(whole);
39    }
40    if cuts.last().is_none_or(|(_, made)| *made < lookups.len()) {
41        cuts.push(("end".to_string(), lookups.len()));
42    }
43    let (mut from, mut hits, mut looked) = (0, 0, 0);
44    for (label, to) in cuts {
45        let tally = Tally::of(&lookups[from..to]);
46        (hits, looked) = (hits + tally.hit, looked + tally.looked());
47        let cum = percent(hits, looked);
48        let tally = line(&tally);
49        let _ = writeln!(out, "sva-cache {label} {tally} cum-hit={cum:.1}%");
50        from = to;
51    }
52    let mut nodes: BTreeMap<&str, Vec<&Lookup>> = BTreeMap::new();
53    for lookup in lookups {
54        nodes.entry(&lookup.node).or_default().push(lookup);
55    }
56    for (node, lookups) in &nodes {
57        let tally = line(&Tally::of(lookups.iter().copied()));
58        let _ = writeln!(out, "sva-cache node {tally} {node}");
59    }
60    let total = Tally::of(lookups);
61    let _ = writeln!(
62        out,
63        "sva-cache total {} hit-rate={:.1}% nodes={} entries={} bytes={}",
64        line(&total),
65        percent(total.hit, total.looked()),
66        nodes.len(),
67        stats.entries,
68        stats.bytes
69    );
70    out
71}