1use std::sync::{Mutex, MutexGuard, PoisonError};
4
5use sva_formula::Hash;
6use sva_samples::Label;
7
8use super::store::{Kept, Stamp};
9use super::{Cache, CachePolicy, Entry, Expected, Payload, PayloadKind};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Outcome {
13 Hit,
14 ComputedStored,
15 ComputedNotStored,
16 ComputedReplaced,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct Lookup {
22 pub node: String,
23 pub key: Hash,
24 pub kind: PayloadKind,
25 pub outcome: Outcome,
26}
27
28#[derive(Clone, Debug, Default, PartialEq)]
30pub struct CacheStats {
31 pub lookups: Vec<Lookup>,
32 pub bytes: u64,
33 pub max_bytes: u64,
34 pub entries: usize,
35 pub evictions: u64,
37}
38
39impl CacheStats {
40 pub fn nodes(&self) -> usize {
42 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
43 names.sort_unstable();
44 names.dedup();
45 names.len()
46 }
47
48 pub fn hits(&self) -> usize {
49 self.count(|o| o == Outcome::Hit)
50 }
51
52 pub fn computed(&self) -> usize {
53 self.count(|o| o != Outcome::Hit)
54 }
55
56 pub fn stored(&self) -> usize {
57 self.count(|o| o == Outcome::ComputedStored)
58 }
59
60 pub fn replaced(&self) -> usize {
61 self.count(|o| o == Outcome::ComputedReplaced)
62 }
63
64 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
65 self.lookups.iter().filter(|l| of(l.outcome)).count()
66 }
67}
68
69pub(crate) struct Recording<'a> {
71 cache: &'a Cache,
72 policy: CachePolicy,
73 tree: u64,
74 evictions: u64,
75 lookups: Mutex<Vec<Lookup>>,
76}
77
78impl<'a> Recording<'a> {
79 pub(crate) fn over(cache: &'a Cache, policy: Option<CachePolicy>) -> Recording<'a> {
81 Recording {
82 cache,
83 policy: policy.unwrap_or_else(|| cache.policy()),
84 tree: cache.begin_tree(),
85 evictions: cache.evictions(),
86 lookups: Mutex::new(Vec::new()),
87 }
88 }
89
90 pub(crate) fn finish(self) -> CacheStats {
91 CacheStats {
92 lookups: self
93 .lookups
94 .into_inner()
95 .unwrap_or_else(PoisonError::into_inner),
96 bytes: self.cache.bytes(),
97 max_bytes: self.cache.max_bytes(),
98 entries: self.cache.entries(),
99 evictions: self.cache.evictions() - self.evictions,
100 }
101 }
102
103 pub(crate) fn at(&self, slot: Option<Hash>, fork: bool, target: bool) -> Lens<'_> {
106 Lens {
107 recording: self,
108 slot,
109 fork,
110 stores: self.stores(fork, target),
111 }
112 }
113
114 pub(crate) fn stores(&self, fork: bool, target: bool) -> bool {
115 self.policy.stores(fork, target)
116 }
117
118 pub(crate) fn holds(&self, key: Hash) -> bool {
119 self.cache.holds(key)
120 }
121
122 fn held(&self) -> MutexGuard<'_, Vec<Lookup>> {
123 self.lookups.lock().unwrap_or_else(PoisonError::into_inner)
124 }
125
126 fn settle(&self, key: Hash, outcome: Outcome) {
127 if let Some(missed) = self
128 .held()
129 .iter_mut()
130 .rev()
131 .find(|l| l.key == key && l.outcome == Outcome::ComputedNotStored)
132 {
133 missed.outcome = outcome;
134 }
135 }
136}
137
138pub(crate) struct Lens<'a> {
139 recording: &'a Recording<'a>,
140 slot: Option<Hash>,
141 fork: bool,
142 stores: bool,
143}
144
145impl Lens<'_> {
146 fn stamp(&self, kind: PayloadKind) -> Stamp {
147 Stamp {
148 tree: self.recording.tree,
149 fork: self.fork,
150 slot: self
151 .slot
152 .map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
153 }
154 }
155
156 pub(crate) fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry> {
157 let found = self
158 .recording
159 .cache
160 .load(key, expected, self.stamp(expected.kind()));
161 self.recording.held().push(Lookup {
162 node: node.to_string(),
163 key,
164 kind: expected.kind(),
165 outcome: match found {
166 Some(_) => Outcome::Hit,
167 None => Outcome::ComputedNotStored,
168 },
169 });
170 found
171 }
172
173 pub(crate) fn store(&self, key: Hash, payload: &Payload, label: Option<&Label>) {
174 if !self.stores {
175 return;
176 }
177 let stamp = self.stamp(payload.kind());
178 match self.recording.cache.store(key, payload, label, stamp) {
179 Kept::Held => self.recording.settle(key, Outcome::ComputedStored),
180 Kept::Replaced => self.recording.settle(key, Outcome::ComputedReplaced),
181 Kept::Refused => {}
182 }
183 }
184}