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