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 reach(&mut self, at: i64) {
102 self.reached.push((at, self.lookups.len()));
103 }
104
105 pub(crate) fn stats(&self) -> CacheStats {
106 let cache = self.cache.as_ref();
107 CacheStats {
108 lookups: self.lookups.clone(),
109 bytes: cache.map_or(0, Cache::bytes),
110 max_bytes: cache.map_or(0, Cache::max_bytes),
111 entries: cache.map_or(0, Cache::entries),
112 evictions: cache.map_or(0, |c| c.evictions() - self.evictions),
113 reached: self.reached.clone(),
114 typed: Vec::new(),
115 planned: Vec::new(),
116 }
117 }
118
119 pub(crate) fn stores(&self, fork: bool, target: bool) -> bool {
120 self.cache.is_some() && self.policy.stores(fork, target)
121 }
122
123 pub(crate) fn mark_every(&self) -> usize {
124 self.cache.as_ref().map_or(usize::MAX, Cache::mark_every)
125 }
126
127 pub(crate) fn stamp(&self, slot: Option<Hash>, fork: bool, kind: PayloadKind) -> Stamp {
129 Stamp {
130 tree: self.tree,
131 fork,
132 slot: slot.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
133 }
134 }
135
136 pub(crate) fn note(
137 &mut self,
138 node: &str,
139 key: Hash,
140 kind: PayloadKind,
141 outcome: Outcome,
142 ) -> usize {
143 self.lookups.push(Lookup {
144 node: node.to_string(),
145 key,
146 kind,
147 outcome,
148 store: None,
149 });
150 self.lookups.len() - 1
151 }
152
153 pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
155 self.cache.as_ref()?.load(key, expected, stamp)
156 }
157
158 pub(crate) fn store(
160 &mut self,
161 (key, noted): (Hash, Option<usize>),
162 payload: Payload,
163 label: Option<&Label>,
164 stamp: Stamp,
165 ) {
166 let Some(cache) = &self.cache else {
167 return;
168 };
169 let outcome = match cache.merge(key, payload, label, stamp) {
170 Kept::Held => Outcome::ComputedStored,
171 Kept::Replaced => Outcome::ComputedReplaced,
172 Kept::Refused => return,
173 };
174 let Some(lookup) = noted.and_then(|at| self.lookups.get_mut(at)) else {
175 return;
176 };
177 match lookup.outcome {
178 Outcome::ComputedNotStored => lookup.outcome = outcome,
179 Outcome::Hit if lookup.kind == PayloadKind::Run => lookup.outcome = Outcome::Extended,
180 _ => {}
181 }
182 }
183}