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