1use sva_formula::Hash;
4use sva_samples::Label;
5
6use super::memory::{Counters, Kept, Memory, Stamp};
7use super::{Entry, Expected, Payload, PayloadKind};
8use crate::recent::Recent;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Outcome {
12 Hit,
13 ComputedStored,
14 ComputedNotStored,
15 ComputedReplaced,
17 Extended,
18 Prefix,
20}
21
22#[derive(Clone, Debug, PartialEq)]
23pub struct Lookup {
24 pub node: String,
25 pub key: Hash,
26 pub kind: PayloadKind,
27 pub outcome: Outcome,
28 pub store: Option<bool>,
30}
31
32#[derive(Clone, Debug, Default, PartialEq)]
34pub struct CacheStats {
35 pub lookups: Vec<Lookup>,
36 pub shed: usize,
38 pub bytes: u64,
39 pub max_bytes: u64,
40 pub entries: usize,
41 pub evictions: u64,
42 pub tier: Counters,
43 pub reached: Vec<(i64, usize)>,
45 pub typed: Vec<String>,
46 pub planned: Vec<String>,
47 pub unstaged: Option<String>,
49}
50
51impl CacheStats {
52 pub fn nodes(&self) -> usize {
53 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
54 names.sort_unstable();
55 names.dedup();
56 names.len()
57 }
58
59 pub fn hits(&self) -> usize {
60 self.lookups
61 .iter()
62 .filter(|l| l.outcome == Outcome::Hit)
63 .count()
64 }
65
66 pub fn computed(&self) -> usize {
68 self.count(|o| o != Outcome::Hit)
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 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
84 let values = self.lookups.iter().filter(|l| l.store.is_none());
85 values.filter(|l| of(l.outcome)).count()
86 }
87}
88
89pub(crate) struct Recording {
90 memory: Memory,
91 tree: u64,
92 since: Counters,
93 failures: u64,
94 lookups: Recent<Lookup>,
95 reached: Option<Vec<(i64, usize)>>,
96}
97
98impl Recording {
99 pub(crate) fn over(memory: &Memory) -> Recording {
100 Recording {
101 memory: memory.clone(),
102 tree: memory.begin_tree(),
103 since: memory.counters(),
104 failures: memory.failures().0,
105 lookups: Recent::keeping(usize::MAX),
106 reached: Some(Vec::new()),
107 }
108 }
109
110 pub(crate) fn latest(self, kept: usize) -> Recording {
111 Recording {
112 lookups: Recent::keeping(kept),
113 reached: None,
114 ..self
115 }
116 }
117
118 pub(crate) fn found(&mut self, lookups: Vec<Lookup>) {
119 for lookup in lookups {
120 self.lookups.push(lookup);
121 }
122 }
123
124 pub(crate) fn reach(&mut self, at: i64) {
125 let made = self.lookups.made();
126 if let Some(reached) = &mut self.reached {
127 reached.push((at, made));
128 }
129 }
130
131 pub(crate) fn stats(&self) -> CacheStats {
132 let memory = &self.memory;
133 let tier = memory.counters().since(self.since);
134 let (failures, why) = memory.failures();
135 let failed = failures > self.failures;
136 CacheStats {
137 lookups: self.lookups.iter().cloned().collect(),
138 shed: self.lookups.shed(),
139 bytes: memory.bytes(),
140 max_bytes: memory.max_bytes(),
141 entries: memory.entries(),
142 evictions: tier.evictions(),
143 tier,
144 reached: self.reached.clone().unwrap_or_default(),
145 typed: Vec::new(),
146 planned: Vec::new(),
147 unstaged: why.filter(|_| failed || memory.blocked()),
148 }
149 }
150
151 pub(crate) fn since(&self) -> Counters {
152 self.memory.counters().since(self.since)
153 }
154
155 pub(crate) fn keeps(&self, fork: bool, target: bool, offered: bool) -> bool {
156 self.memory.keeps(fork, target, offered)
157 }
158
159 pub(crate) fn mark_every(&self) -> usize {
160 self.memory.mark_every()
161 }
162
163 pub(crate) fn stamp(&self, slot: Option<Hash>, fork: bool, kind: PayloadKind) -> Stamp {
165 Stamp {
166 tree: self.tree,
167 fork,
168 slot: slot.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
169 }
170 }
171
172 pub(crate) fn note(
173 &mut self,
174 node: &str,
175 key: Hash,
176 kind: PayloadKind,
177 outcome: Outcome,
178 ) -> usize {
179 self.lookups.push(Lookup {
180 node: node.to_string(),
181 key,
182 kind,
183 outcome,
184 store: None,
185 })
186 }
187
188 pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
190 self.memory.load(key, expected, stamp)
191 }
192
193 pub(crate) fn store(
195 &mut self,
196 (key, noted): (Hash, Option<usize>),
197 payload: Payload,
198 label: Option<&Label>,
199 stamp: Stamp,
200 ) {
201 let outcome = match self.memory.merge(key, payload, label, stamp) {
202 Kept::Held => Outcome::ComputedStored,
203 Kept::Replaced => Outcome::ComputedReplaced,
204 Kept::Refused => return,
205 };
206 let Some(lookup) = noted.and_then(|at| self.lookups.get_mut(at)) else {
207 return;
208 };
209 match lookup.outcome {
210 Outcome::ComputedNotStored => lookup.outcome = outcome,
211 Outcome::Hit if lookup.kind == PayloadKind::Run => lookup.outcome = Outcome::Extended,
212 _ => {}
213 }
214 }
215}