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 pub unslotted: Option<String>,
50}
51
52impl CacheStats {
53 pub fn nodes(&self) -> usize {
54 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
55 names.sort_unstable();
56 names.dedup();
57 names.len()
58 }
59
60 pub fn hits(&self) -> usize {
61 self.lookups
62 .iter()
63 .filter(|l| l.outcome == Outcome::Hit)
64 .count()
65 }
66
67 pub fn computed(&self) -> usize {
69 self.count(|o| o != Outcome::Hit)
70 }
71
72 pub fn stored(&self) -> usize {
73 self.count(|o| o == Outcome::ComputedStored)
74 }
75
76 pub fn replaced(&self) -> usize {
77 self.count(|o| o == Outcome::ComputedReplaced)
78 }
79
80 pub fn extended(&self) -> usize {
81 self.count(|o| o == Outcome::Extended)
82 }
83
84 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
85 let values = self.lookups.iter().filter(|l| l.store.is_none());
86 values.filter(|l| of(l.outcome)).count()
87 }
88}
89
90pub(crate) struct Recording {
91 memory: Memory,
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 since: memory.counters(),
103 failures: memory.failures().0,
104 lookups: Recent::keeping(usize::MAX),
105 reached: Some(Vec::new()),
106 }
107 }
108
109 pub(crate) fn latest(self, kept: usize) -> Recording {
110 Recording {
111 lookups: Recent::keeping(kept),
112 reached: None,
113 ..self
114 }
115 }
116
117 pub(crate) fn found(&mut self, lookups: Vec<Lookup>) {
118 for lookup in lookups {
119 self.lookups.push(lookup);
120 }
121 }
122
123 pub(crate) fn reach(&mut self, at: i64) {
124 let made = self.lookups.made();
125 if let Some(reached) = &mut self.reached {
126 reached.push((at, made));
127 }
128 }
129
130 pub(crate) fn stats(&self) -> CacheStats {
131 let memory = &self.memory;
132 let tier = memory.counters().since(self.since);
133 let (failures, why) = memory.failures();
134 let failed = failures > self.failures;
135 CacheStats {
136 lookups: self.lookups.iter().cloned().collect(),
137 shed: self.lookups.shed(),
138 bytes: memory.bytes(),
139 max_bytes: memory.max_bytes(),
140 entries: memory.entries(),
141 evictions: tier.evictions(),
142 tier,
143 reached: self.reached.clone().unwrap_or_default(),
144 typed: Vec::new(),
145 planned: Vec::new(),
146 unstaged: why.filter(|_| failed || memory.blocked()),
147 unslotted: None,
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) -> bool {
156 self.memory.keeps()
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>, kind: PayloadKind) -> Stamp {
165 Stamp {
166 slot: slot.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
167 }
168 }
169
170 pub(crate) fn note(
171 &mut self,
172 node: &str,
173 key: Hash,
174 kind: PayloadKind,
175 outcome: Outcome,
176 ) -> usize {
177 self.lookups.push(Lookup {
178 node: node.to_string(),
179 key,
180 kind,
181 outcome,
182 store: None,
183 })
184 }
185
186 pub(crate) fn load(&self, key: Hash, expected: Expected) -> Option<Entry> {
188 self.memory.load(key, expected)
189 }
190
191 pub(crate) fn store(
193 &mut self,
194 (key, noted): (Hash, Option<usize>),
195 payload: Payload,
196 label: Option<&Label>,
197 stamp: Stamp,
198 ) {
199 let outcome = match self.memory.merge(key, payload, label, stamp) {
200 Kept::Held => Outcome::ComputedStored,
201 Kept::Replaced => Outcome::ComputedReplaced,
202 Kept::Refused => return,
203 };
204 let Some(lookup) = noted.and_then(|at| self.lookups.get_mut(at)) else {
205 return;
206 };
207 match lookup.outcome {
208 Outcome::ComputedNotStored => lookup.outcome = outcome,
209 Outcome::Hit if lookup.kind == PayloadKind::Run => lookup.outcome = Outcome::Extended,
210 _ => {}
211 }
212 }
213}