sva_engine/cache/
stats.rs1use std::path::Path;
4use std::sync::{Mutex, MutexGuard, PoisonError};
5use std::time::Duration;
6
7use sva_formula::Hash;
8use sva_samples::{FilterTrace, Label};
9
10use super::{Cache, Entry, Expected, Payload, PayloadKind, Tier};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Outcome {
14 Hit(Tier),
15 ComputedStored,
16 ComputedNotStored,
17}
18
19#[derive(Clone, Debug, PartialEq)]
20pub struct Lookup {
21 pub node: String,
22 pub key: Hash,
23 pub kind: PayloadKind,
24 pub outcome: Outcome,
25}
26
27#[derive(Clone, Debug, Default, PartialEq)]
29pub struct CacheStats {
30 pub lookups: Vec<Lookup>,
31}
32
33impl CacheStats {
34 pub fn nodes(&self) -> usize {
36 let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
37 names.sort_unstable();
38 names.dedup();
39 names.len()
40 }
41
42 pub fn hits(&self) -> usize {
43 self.count(|o| matches!(o, Outcome::Hit(_)))
44 }
45
46 pub fn hits_in(&self, tier: Tier) -> usize {
47 self.count(|o| o == Outcome::Hit(tier))
48 }
49
50 pub fn computed(&self) -> usize {
51 self.count(|o| !matches!(o, Outcome::Hit(_)))
52 }
53
54 pub fn stored(&self) -> usize {
55 self.count(|o| o == Outcome::ComputedStored)
56 }
57
58 fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
59 self.lookups.iter().filter(|l| of(l.outcome)).count()
60 }
61}
62
63pub(crate) struct Recording<'a> {
65 inner: &'a dyn Cache,
66 lookups: Mutex<Vec<Lookup>>,
67}
68
69impl<'a> Recording<'a> {
70 pub(crate) fn over(inner: &'a dyn Cache) -> Recording<'a> {
71 Recording {
72 inner,
73 lookups: Mutex::new(Vec::new()),
74 }
75 }
76
77 pub(crate) fn finish(self) -> CacheStats {
78 CacheStats {
79 lookups: self
80 .lookups
81 .into_inner()
82 .unwrap_or_else(PoisonError::into_inner),
83 }
84 }
85
86 fn held(&self) -> MutexGuard<'_, Vec<Lookup>> {
87 self.lookups.lock().unwrap_or_else(PoisonError::into_inner)
88 }
89}
90
91impl Cache for Recording<'_> {
92 fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry> {
93 let found = self.inner.load(key, node, expected);
94 self.held().push(Lookup {
95 node: node.to_string(),
96 key,
97 kind: expected.kind(),
98 outcome: found
99 .as_ref()
100 .map_or(Outcome::ComputedNotStored, |e| Outcome::Hit(e.tier)),
101 });
102 found
103 }
104
105 fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>) {
106 if let Some(missed) = self
107 .held()
108 .iter_mut()
109 .rev()
110 .find(|l| l.key == key && l.outcome == Outcome::ComputedNotStored)
111 {
112 missed.outcome = Outcome::ComputedStored;
113 }
114 self.inner.store(key, payload, traces, label);
115 }
116
117 fn holds(&self, key: Hash) -> bool {
118 self.inner.holds(key)
119 }
120
121 fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool {
122 self.inner.worth_storing(cost, bytes, kind)
123 }
124
125 fn sweep(&self) {
126 self.inner.sweep();
127 }
128
129 fn held_bytes(&self) -> u64 {
130 self.inner.held_bytes()
131 }
132
133 fn evicted_bytes(&self) -> u64 {
134 self.inner.evicted_bytes()
135 }
136
137 fn faults(&self) -> u64 {
138 self.inner.faults()
139 }
140
141 fn max_bytes(&self) -> u64 {
142 self.inner.max_bytes()
143 }
144
145 fn dir(&self) -> Option<&Path> {
146 self.inner.dir()
147 }
148}