1use std::path::Path;
4use std::sync::{Mutex, MutexGuard, PoisonError};
5use std::time::Duration;
6
7use sva_formula::Hash;
8use sva_samples::{FilterTrace, Label};
9
10use super::{Cache, Entry, Expected, Payload, PayloadKind, Put, Slots, Tier};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Outcome {
14 Hit(Tier),
15 ComputedStored,
16 ComputedNotStored,
17 ComputedSlotted,
19 ComputedReplaced,
21}
22
23#[derive(Clone, Debug, PartialEq)]
24pub struct Lookup {
25 pub node: String,
26 pub key: Hash,
27 pub kind: PayloadKind,
28 pub outcome: Outcome,
29}
30
31#[derive(Clone, Debug, Default, PartialEq)]
33pub struct CacheStats {
34 pub lookups: Vec<Lookup>,
35}
36
37impl CacheStats {
38 pub fn nodes(&self) -> usize {
40 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
41 names.sort_unstable();
42 names.dedup();
43 names.len()
44 }
45
46 pub fn hits(&self) -> usize {
47 self.count(|o| matches!(o, Outcome::Hit(_)))
48 }
49
50 pub fn hits_in(&self, tier: Tier) -> usize {
51 self.count(|o| o == Outcome::Hit(tier))
52 }
53
54 pub fn computed(&self) -> usize {
55 self.count(|o| !matches!(o, Outcome::Hit(_)))
56 }
57
58 pub fn stored(&self) -> usize {
59 self.count(|o| o == Outcome::ComputedStored)
60 }
61
62 pub fn slotted(&self) -> usize {
63 self.count(|o| o == Outcome::ComputedSlotted)
64 }
65
66 pub fn replaced(&self) -> usize {
67 self.count(|o| o == Outcome::ComputedReplaced)
68 }
69
70 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
71 self.lookups.iter().filter(|l| of(l.outcome)).count()
72 }
73}
74
75pub(crate) struct Recording<'a> {
77 inner: &'a dyn Cache,
78 slots: Option<&'a Slots>,
79 lookups: Mutex<Vec<Lookup>>,
80}
81
82impl<'a> Recording<'a> {
83 pub(crate) fn over(inner: &'a dyn Cache, slots: Option<&'a Slots>) -> Recording<'a> {
84 Recording {
85 inner,
86 slots,
87 lookups: Mutex::new(Vec::new()),
88 }
89 }
90
91 pub(crate) fn finish(self) -> CacheStats {
92 CacheStats {
93 lookups: self
94 .lookups
95 .into_inner()
96 .unwrap_or_else(PoisonError::into_inner),
97 }
98 }
99
100 pub(crate) fn at(&'a self, slot: Option<Hash>) -> Lens<'a> {
103 Lens {
104 recording: self,
105 slot,
106 }
107 }
108
109 fn held(&self) -> MutexGuard<'_, Vec<Lookup>> {
110 self.lookups.lock().unwrap_or_else(PoisonError::into_inner)
111 }
112
113 fn record(&self, key: Hash, node: &str, expected: Expected, found: Option<&Entry>) {
114 self.held().push(Lookup {
115 node: node.to_string(),
116 key,
117 kind: expected.kind(),
118 outcome: found.map_or(Outcome::ComputedNotStored, |e| Outcome::Hit(e.tier)),
119 });
120 }
121
122 fn settle(&self, key: Hash, outcome: Outcome) {
123 if let Some(missed) = self
124 .held()
125 .iter_mut()
126 .rev()
127 .find(|l| l.key == key && l.outcome == Outcome::ComputedNotStored)
128 {
129 missed.outcome = outcome;
130 }
131 }
132}
133
134pub(crate) struct Lens<'a> {
135 recording: &'a Recording<'a>,
136 slot: Option<Hash>,
137}
138
139impl Lens<'_> {
140 fn slotted(&self, kind: PayloadKind) -> Option<(&Slots, Hash)> {
141 let slot = super::mixed(self.slot?, &[kind as u64, 0x73_6c_6f_74]);
142 Some((self.recording.slots?, slot))
143 }
144}
145
146impl Cache for Lens<'_> {
147 fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry> {
148 let inner = self.recording.inner;
149 let found = match self.slot {
150 None => inner.load(key, node, expected),
151 Some(_) => inner.peek(key, node, expected).or_else(|| {
152 let (slots, slot) = self.slotted(expected.kind())?;
153 slots.get(slot, key, node, expected)
154 }),
155 };
156 self.recording.record(key, node, expected, found.as_ref());
157 found
158 }
159
160 fn peek(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry> {
161 self.recording.inner.peek(key, node, expected)
162 }
163
164 fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>) {
165 if self.slot.is_none() {
166 self.recording.settle(key, Outcome::ComputedStored);
167 return self.recording.inner.store(key, payload, traces, label);
168 }
169 let Some((slots, slot)) = self.slotted(payload.kind()) else {
170 return;
171 };
172 match slots.put(slot, key, payload, traces, label) {
173 Put::Slotted => self.recording.settle(key, Outcome::ComputedSlotted),
174 Put::Replaced => self.recording.settle(key, Outcome::ComputedReplaced),
175 Put::Refused => {}
176 }
177 }
178
179 fn holds(&self, key: Hash) -> bool {
180 self.recording.inner.holds(key)
181 }
182
183 fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool {
185 match self.slot {
186 None => self.recording.inner.worth_storing(cost, bytes, kind),
187 Some(_) => kind != PayloadKind::Symbolic && self.recording.slots.is_some(),
188 }
189 }
190
191 fn sweep(&self) {
192 if self.slot.is_none() {
193 self.recording.inner.sweep();
194 }
195 }
196
197 fn held_bytes(&self) -> u64 {
198 self.recording.inner.held_bytes()
199 }
200
201 fn evicted_bytes(&self) -> u64 {
202 self.recording.inner.evicted_bytes()
203 }
204
205 fn faults(&self) -> u64 {
206 self.recording.inner.faults()
207 }
208
209 fn max_bytes(&self) -> u64 {
210 self.recording.inner.max_bytes()
211 }
212
213 fn dir(&self) -> Option<&Path> {
214 self.recording.inner.dir()
215 }
216}