Skip to main content

sva_engine/cache/
stats.rs

1// Concern: what one render asked its store, and what each lookup came to | Non-concern: deciding what to store (the store's own worth_storing) | IO: (loads, stores) -> CacheStats
2
3use 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/// Every lookup in the order the render made it.
28#[derive(Clone, Debug, Default, PartialEq)]
29pub struct CacheStats {
30    pub lookups: Vec<Lookup>,
31}
32
33impl CacheStats {
34    /// Distinct nodes looked up, however many kinds each asked for.
35    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
63/// Wraps the store a render was handed, so the render itself never says what it looked up.
64pub(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}