1use std::collections::BTreeMap;
4use std::fmt::Write;
5
6use super::{CacheStats, Lookup, Outcome};
7
8#[derive(Default)]
9struct Tally {
10 hit: usize,
11 miss: usize,
12 prefix: usize,
13 new: usize,
14 reused: usize,
15}
16
17impl Tally {
18 fn of<'l>(lookups: impl IntoIterator<Item = &'l Lookup>) -> Tally {
19 let mut tally = Tally::default();
20 for lookup in lookups {
21 tally.add(lookup);
22 }
23 tally
24 }
25
26 fn add(&mut self, lookup: &Lookup) {
28 let outcome = lookup.outcome;
29 match outcome {
30 Outcome::Hit => self.hit += 1,
31 Outcome::Prefix => self.prefix += 1,
32 Outcome::Reused => self.reused += 1,
33 _ => self.miss += 1,
34 }
35 if matches!(
36 outcome,
37 Outcome::ComputedStored | Outcome::ComputedReplaced | Outcome::Extended
38 ) {
39 self.new += 1;
40 }
41 }
42
43 fn looked(&self) -> usize {
44 self.hit + self.miss + self.prefix
45 }
46}
47
48impl std::fmt::Display for Tally {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(
51 f,
52 "hit={} miss={} prefix={} new={} reused={}",
53 self.hit, self.miss, self.prefix, self.new, self.reused
54 )
55 }
56}
57
58fn percent(part: usize, whole: usize) -> f64 {
59 match whole {
60 0 => 0.0,
61 _ => 100.0 * part as f64 / whole as f64,
62 }
63}
64
65pub fn cache_log(stats: &CacheStats, rate: u32) -> String {
68 let (lookups, reached) = (&stats.lookups, &stats.reached);
69 let mut out = String::new();
70 let mut cuts: Vec<(String, usize)> = Vec::new();
71 let mut second = None;
72 for (k, &(at, made)) in reached.iter().enumerate() {
73 let whole = at.div_euclid(i64::from(rate));
74 match second {
75 None => cuts.push(("pass".to_string(), made)),
76 Some(last) if whole > last || k + 1 == reached.len() => {
77 cuts.push((format!("t={:.3}s", at as f64 / f64::from(rate)), made));
78 }
79 Some(_) => continue,
80 }
81 second = Some(whole);
82 }
83 if cuts.last().is_none_or(|(_, made)| *made < lookups.len()) {
84 cuts.push(("end".to_string(), lookups.len()));
85 }
86 let (mut from, mut hits, mut looked) = (0, 0, 0);
87 for (label, to) in cuts {
88 let tally = Tally::of(&lookups[from..to]);
89 (hits, looked) = (hits + tally.hit, looked + tally.looked());
90 let cum = percent(hits, looked);
91 let _ = writeln!(out, "sva-cache {label} {tally} cum-hit={cum:.1}%");
92 from = to;
93 }
94 let mut nodes: BTreeMap<&str, Tally> = BTreeMap::new();
95 for lookup in lookups {
96 nodes.entry(&lookup.node).or_default().add(lookup);
97 }
98 for (node, tally) in &nodes {
99 let _ = writeln!(out, "sva-cache node {tally} {node}");
100 }
101 let total = Tally::of(lookups);
102 let _ = writeln!(
103 out,
104 "sva-cache total {total} hit-rate={:.1}% nodes={} entries={} bytes={}",
105 percent(total.hit, total.looked()),
106 nodes.len(),
107 stats.entries,
108 stats.bytes
109 );
110 out
111}