1use std::collections::HashMap;
4
5use sva_formula::Hash;
6
7use super::PayloadKind;
8use super::memory::{Counters, Kept, Memory};
9use crate::recent::Recent;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Outcome {
13 Hit,
14 ComputedStored,
15 ComputedNotStored,
16 ComputedReplaced,
18 Extended,
20 Prefix,
22 Reused,
23}
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct Lookup {
27 pub node: String,
28 pub key: Hash,
29 pub kind: PayloadKind,
30 pub outcome: Outcome,
31}
32
33#[derive(Clone, Debug, Default, PartialEq)]
35pub struct CacheStats {
36 pub lookups: Vec<Lookup>,
37 pub shed: usize,
39 pub bytes: u64,
40 pub max_bytes: u64,
41 pub entries: usize,
42 pub evictions: u64,
43 pub tier: Counters,
44 pub reached: Vec<(i64, usize)>,
46 pub typed: Vec<String>,
47 pub planned: Vec<String>,
48 pub analysed: usize,
50 pub unstaged: Option<String>,
52 pub unslotted: Option<String>,
53}
54
55impl CacheStats {
56 pub fn nodes(&self) -> usize {
57 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
58 names.sort_unstable();
59 names.dedup();
60 names.len()
61 }
62
63 pub fn hits(&self) -> usize {
64 self.count(|o| o == Outcome::Hit)
65 }
66
67 pub fn computed(&self) -> usize {
68 self.count(|o| !matches!(o, Outcome::Hit | Outcome::Reused))
69 }
70
71 pub fn stored(&self) -> usize {
72 self.count(|o| o == Outcome::ComputedStored)
73 }
74
75 pub fn replaced(&self) -> usize {
76 self.count(|o| o == Outcome::ComputedReplaced)
77 }
78
79 pub fn extended(&self) -> usize {
80 self.count(|o| o == Outcome::Extended)
81 }
82
83 pub fn reused(&self) -> usize {
84 self.count(|o| o == Outcome::Reused)
85 }
86
87 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
88 self.lookups.iter().filter(|l| of(l.outcome)).count()
89 }
90}
91
92pub(crate) struct Recording {
94 since: Counters,
95 failures: u64,
96 lookups: Recent<Lookup>,
97 reached: Option<Vec<(i64, usize)>>,
98 looked: HashMap<Hash, (u64, usize)>,
100 change: u64,
101}
102
103impl Recording {
104 pub(crate) fn over(memory: &Memory) -> Recording {
105 Recording {
106 since: memory.counters(),
107 failures: memory.failures().0,
108 lookups: Recent::keeping(usize::MAX),
109 reached: Some(Vec::new()),
110 looked: HashMap::new(),
111 change: 0,
112 }
113 }
114
115 pub(crate) fn latest(self, kept: usize) -> Recording {
116 Recording {
117 lookups: Recent::keeping(kept),
118 reached: None,
119 ..self
120 }
121 }
122
123 pub(crate) fn begin(&mut self) {
125 self.change += 1;
126 let shed = self.lookups.shed();
127 self.looked.retain(|_, (_, at)| *at >= shed);
128 }
129
130 pub(crate) fn reach(&mut self, at: i64) {
131 let made = self.lookups.made();
132 if let Some(reached) = &mut self.reached {
133 reached.push((at, made));
134 }
135 }
136
137 pub(crate) fn stats(&self, memory: &Memory) -> CacheStats {
138 let tier = self.since(memory);
139 let (failures, why) = memory.failures();
140 let failed = failures > self.failures;
141 CacheStats {
142 lookups: self.lookups.iter().cloned().collect(),
143 shed: self.lookups.shed(),
144 bytes: memory.bytes(),
145 max_bytes: memory.max_bytes(),
146 entries: memory.entries(),
147 evictions: tier.evictions(),
148 tier,
149 reached: self.reached.clone().unwrap_or_default(),
150 typed: Vec::new(),
151 planned: Vec::new(),
152 analysed: 0,
153 unstaged: why.filter(|_| failed || memory.blocked()),
154 unslotted: None,
155 }
156 }
157
158 pub(crate) fn since(&self, memory: &Memory) -> Counters {
159 memory.counters().since(self.since)
160 }
161
162 pub(super) fn answered(&mut self, key: Hash, node: &str, kind: PayloadKind, outcome: Outcome) {
164 let now = self.change;
165 let held = self.looked(key).filter(|(change, _)| *change == now);
166 if let Some(lookup) = held.and_then(|(_, at)| self.lookups.get_mut(at)) {
167 let found = |o: Outcome| match o {
168 Outcome::Hit => 2,
169 Outcome::Prefix => 1,
170 _ => 0,
171 };
172 lookup.kind = kind;
173 if found(outcome) > found(lookup.outcome) {
174 lookup.outcome = outcome;
175 }
176 return;
177 }
178 let lookup = Lookup {
179 node: node.to_string(),
180 key,
181 kind,
182 outcome,
183 };
184 let at = self.lookups.push(lookup);
185 self.looked.insert(key, (self.change, at));
186 }
187
188 pub(super) fn kept(&mut self, key: Hash, kept: Kept) {
189 let Some(lookup) = self
190 .looked(key)
191 .and_then(|(_, at)| self.lookups.get_mut(at))
192 else {
193 return;
194 };
195 lookup.outcome = match (lookup.outcome, kept) {
196 (Outcome::ComputedNotStored, Kept::Held) => Outcome::ComputedStored,
197 (Outcome::ComputedNotStored, Kept::Replaced) => Outcome::ComputedReplaced,
198 (Outcome::Hit, Kept::Held | Kept::Replaced) => Outcome::Extended,
199 (outcome, _) => outcome,
200 };
201 }
202
203 pub(crate) fn reused(&mut self, key: Hash, node: &str, kind: PayloadKind) {
204 if self.looked(key).is_none() {
205 return;
206 }
207 self.lookups.push(Lookup {
208 node: node.to_string(),
209 key,
210 kind,
211 outcome: Outcome::Reused,
212 });
213 }
214
215 fn looked(&self, key: Hash) -> Option<(u64, usize)> {
216 self.looked.get(&key).copied()
217 }
218}