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