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