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