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 pub from_store: bool,
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub struct Tally {
39 pub hit: usize,
40 pub from_store: usize,
42 pub miss: usize,
43 pub prefix: usize,
44 pub new: usize,
45 pub reused: usize,
46}
47
48impl Tally {
49 pub fn of<'l>(lookups: impl IntoIterator<Item = &'l Lookup>) -> Tally {
50 let mut tally = Tally::default();
51 for lookup in lookups {
52 tally = tally.plus(Tally::one(lookup));
53 }
54 tally
55 }
56
57 fn one(lookup: &Lookup) -> Tally {
58 let outcome = lookup.outcome;
59 let is = |held: bool| usize::from(held);
60 Tally {
61 hit: is(outcome == Outcome::Hit),
62 from_store: is(outcome == Outcome::Hit && lookup.from_store),
63 prefix: is(outcome == Outcome::Prefix),
64 reused: is(outcome == Outcome::Reused),
65 miss: is(!matches!(
66 outcome,
67 Outcome::Hit | Outcome::Prefix | Outcome::Reused
68 )),
69 new: is(matches!(
70 outcome,
71 Outcome::ComputedStored | Outcome::ComputedReplaced | Outcome::Extended
72 )),
73 }
74 }
75
76 pub fn looked(&self) -> usize {
77 self.hit + self.miss + self.prefix
78 }
79
80 pub fn plus(self, more: Tally) -> Tally {
81 self.each(more, usize::wrapping_add)
82 }
83
84 pub fn minus(self, less: Tally) -> Tally {
86 self.each(less, usize::wrapping_sub)
87 }
88
89 fn each(self, other: Tally, by: fn(usize, usize) -> usize) -> Tally {
90 Tally {
91 hit: by(self.hit, other.hit),
92 from_store: by(self.from_store, other.from_store),
93 miss: by(self.miss, other.miss),
94 prefix: by(self.prefix, other.prefix),
95 new: by(self.new, other.new),
96 reused: by(self.reused, other.reused),
97 }
98 }
99}
100
101#[derive(Clone, Debug, Default, PartialEq)]
103pub struct CacheStats {
104 pub lookups: Vec<Lookup>,
105 pub shed: usize,
107 pub bytes: u64,
108 pub max_bytes: u64,
109 pub entries: usize,
110 pub evictions: u64,
111 pub tier: Counters,
112 pub reached: Vec<(i64, usize)>,
114 pub typed: Vec<String>,
115 pub planned: Vec<String>,
116 pub analysed: usize,
118 pub unstaged: Option<String>,
120 pub unslotted: Option<String>,
121}
122
123impl CacheStats {
124 pub fn nodes(&self) -> usize {
125 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
126 names.sort_unstable();
127 names.dedup();
128 names.len()
129 }
130
131 pub fn hits(&self) -> usize {
132 self.count(|o| o == Outcome::Hit)
133 }
134
135 pub fn computed(&self) -> usize {
136 self.count(|o| !matches!(o, Outcome::Hit | Outcome::Reused))
137 }
138
139 pub fn stored(&self) -> usize {
140 self.count(|o| o == Outcome::ComputedStored)
141 }
142
143 pub fn replaced(&self) -> usize {
144 self.count(|o| o == Outcome::ComputedReplaced)
145 }
146
147 pub fn extended(&self) -> usize {
148 self.count(|o| o == Outcome::Extended)
149 }
150
151 pub fn reused(&self) -> usize {
152 self.count(|o| o == Outcome::Reused)
153 }
154
155 pub fn tally(&self) -> Tally {
156 Tally::of(&self.lookups)
157 }
158
159 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
160 self.lookups.iter().filter(|l| of(l.outcome)).count()
161 }
162}
163
164pub(crate) struct Recording {
166 since: Counters,
167 began: u64,
169 failures: u64,
170 lookups: Recent<Lookup>,
171 shed: Tally,
172 reached: Option<Vec<(i64, usize)>>,
173 looked: HashMap<Hash, (u64, usize)>,
175 change: u64,
176}
177
178impl Recording {
179 pub(crate) fn over(memory: &Memory) -> Recording {
180 Recording {
181 since: memory.counters(),
182 began: memory.clock(),
183 failures: memory.failures().0,
184 lookups: Recent::keeping(usize::MAX),
185 shed: Tally::default(),
186 reached: Some(Vec::new()),
187 looked: HashMap::new(),
188 change: 0,
189 }
190 }
191
192 pub(crate) fn latest(self, kept: usize) -> Recording {
193 Recording {
194 lookups: Recent::keeping(kept),
195 reached: None,
196 ..self
197 }
198 }
199
200 pub(crate) fn begin(&mut self) {
202 self.change += 1;
203 let shed = self.lookups.shed();
204 self.looked.retain(|_, (_, at)| *at >= shed);
205 }
206
207 pub(crate) fn reach(&mut self, at: i64) {
208 let made = self.lookups.made();
209 if let Some(reached) = &mut self.reached {
210 reached.push((at, made));
211 }
212 }
213
214 pub(crate) fn stats(&self, memory: &Memory) -> CacheStats {
215 let tier = self.since(memory);
216 let (failures, why) = memory.failures();
217 let failed = failures > self.failures;
218 CacheStats {
219 lookups: self.lookups.iter().cloned().collect(),
220 shed: self.lookups.shed(),
221 bytes: memory.bytes(),
222 max_bytes: memory.max_bytes(),
223 entries: memory.entries(),
224 evictions: tier.evictions(),
225 tier,
226 reached: self.reached.clone().unwrap_or_default(),
227 typed: Vec::new(),
228 planned: Vec::new(),
229 analysed: 0,
230 unstaged: why.filter(|_| failed || memory.blocked()),
231 unslotted: None,
232 }
233 }
234
235 pub(crate) fn began(&self) -> u64 {
236 self.began
237 }
238
239 pub(crate) fn stored(&self, key: Hash) -> bool {
241 let lookup = self.looked(key).and_then(|(_, at)| self.lookups.get(at));
242 lookup.is_some_and(|lookup| lookup.from_store)
243 }
244
245 pub(crate) fn since(&self, memory: &Memory) -> Counters {
246 memory.counters().since(self.since)
247 }
248
249 pub(crate) fn tally(&self) -> Tally {
250 self.shed.plus(Tally::of(self.lookups.iter()))
251 }
252
253 fn made(&mut self, lookup: Lookup) -> usize {
254 let (at, shed) = self.lookups.pushed(lookup);
255 if let Some(shed) = shed {
256 self.shed = self.shed.plus(Tally::one(&shed));
257 }
258 at
259 }
260
261 pub(super) fn answered(
263 &mut self,
264 (key, node): (Hash, &str),
265 kind: PayloadKind,
266 (outcome, from_store): (Outcome, bool),
267 ) {
268 let now = self.change;
269 let held = self.looked(key).filter(|(change, _)| *change == now);
270 if let Some(lookup) = held.and_then(|(_, at)| self.lookups.get_mut(at)) {
271 let found = |o: Outcome| match o {
272 Outcome::Hit => 2,
273 Outcome::Prefix => 1,
274 _ => 0,
275 };
276 lookup.kind = kind;
277 if found(outcome) > found(lookup.outcome) {
278 (lookup.outcome, lookup.from_store) = (outcome, from_store);
279 }
280 return;
281 }
282 let lookup = Lookup {
283 node: node.to_string(),
284 key,
285 kind,
286 outcome,
287 from_store,
288 };
289 let at = self.made(lookup);
290 self.looked.insert(key, (self.change, at));
291 }
292
293 pub(super) fn kept(&mut self, key: Hash, kept: Kept) {
294 let Some(lookup) = self
295 .looked(key)
296 .and_then(|(_, at)| self.lookups.get_mut(at))
297 else {
298 return;
299 };
300 lookup.outcome = match (lookup.outcome, kept) {
301 (Outcome::ComputedNotStored, Kept::Held) => Outcome::ComputedStored,
302 (Outcome::ComputedNotStored, Kept::Replaced) => Outcome::ComputedReplaced,
303 (Outcome::Hit, Kept::Held | Kept::Replaced) => Outcome::Extended,
304 (outcome, _) => outcome,
305 };
306 }
307
308 pub(crate) fn reused(&mut self, key: Hash, node: &str, kind: PayloadKind) {
309 if self.looked(key).is_none() {
310 return;
311 }
312 self.made(Lookup {
313 node: node.to_string(),
314 key,
315 kind,
316 outcome: Outcome::Reused,
317 from_store: false,
318 });
319 }
320
321 fn looked(&self, key: Hash) -> Option<(u64, usize)> {
322 self.looked.get(&key).copied()
323 }
324}