Skip to main content

sva_engine/cache/
stats.rs

1// Concern: what one render asked of its values and memory, how far its output got, and what each lookup came to | Non-concern: what memory evicts (memory.rs) | IO: (loads, stores) -> CacheStats
2
3use sva_formula::Hash;
4use sva_samples::Label;
5
6use super::memory::{Counters, Kept, Memory, Stamp};
7use super::{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    /// A volatile node's value, stored in place of its last one.
16    ComputedReplaced,
17    Extended,
18    /// Found only up to a switch.
19    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    /// What memory answered a node with.
29    pub store: Option<bool>,
30}
31
32/// Every lookup in order, or a stream's latest, and memory as the render left it.
33#[derive(Clone, Debug, Default, PartialEq)]
34pub struct CacheStats {
35    pub lookups: Vec<Lookup>,
36    /// Lookups made before `lookups`.
37    pub shed: usize,
38    pub bytes: u64,
39    pub max_bytes: u64,
40    pub entries: usize,
41    pub evictions: u64,
42    pub tier: Counters,
43    /// Each output sample a pull reached, and how many lookups had been made by then.
44    pub reached: Vec<(i64, usize)>,
45    pub typed: Vec<String>,
46    pub planned: Vec<String>,
47    /// Why memory stopped writing to the disk meanwhile.
48    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.lookups
61            .iter()
62            .filter(|l| l.outcome == Outcome::Hit)
63            .count()
64    }
65
66    /// Every value computed: a node memory missed computes nothing until its values do.
67    pub fn computed(&self) -> usize {
68        self.count(|o| o != Outcome::Hit)
69    }
70
71    pub fn stored(&self) -> usize {
72        self.count(|o| o == Outcome::ComputedStored)
73    }
74
75    pub fn replaced(&self) -> usize {
76        self.count(|o| o == Outcome::ComputedReplaced)
77    }
78
79    pub fn extended(&self) -> usize {
80        self.count(|o| o == Outcome::Extended)
81    }
82
83    fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
84        let values = self.lookups.iter().filter(|l| l.store.is_none());
85        values.filter(|l| of(l.outcome)).count()
86    }
87}
88
89pub(crate) struct Recording {
90    memory: Memory,
91    tree: u64,
92    since: Counters,
93    failures: u64,
94    lookups: Recent<Lookup>,
95    reached: Option<Vec<(i64, usize)>>,
96}
97
98impl Recording {
99    pub(crate) fn over(memory: &Memory) -> Recording {
100        Recording {
101            memory: memory.clone(),
102            tree: memory.begin_tree(),
103            since: memory.counters(),
104            failures: memory.failures().0,
105            lookups: Recent::keeping(usize::MAX),
106            reached: Some(Vec::new()),
107        }
108    }
109
110    pub(crate) fn latest(self, kept: usize) -> Recording {
111        Recording {
112            lookups: Recent::keeping(kept),
113            reached: None,
114            ..self
115        }
116    }
117
118    pub(crate) fn found(&mut self, lookups: Vec<Lookup>) {
119        for lookup in lookups {
120            self.lookups.push(lookup);
121        }
122    }
123
124    pub(crate) fn reach(&mut self, at: i64) {
125        let made = self.lookups.made();
126        if let Some(reached) = &mut self.reached {
127            reached.push((at, made));
128        }
129    }
130
131    pub(crate) fn stats(&self) -> CacheStats {
132        let memory = &self.memory;
133        let tier = memory.counters().since(self.since);
134        let (failures, why) = memory.failures();
135        let failed = failures > self.failures;
136        CacheStats {
137            lookups: self.lookups.iter().cloned().collect(),
138            shed: self.lookups.shed(),
139            bytes: memory.bytes(),
140            max_bytes: memory.max_bytes(),
141            entries: memory.entries(),
142            evictions: tier.evictions(),
143            tier,
144            reached: self.reached.clone().unwrap_or_default(),
145            typed: Vec::new(),
146            planned: Vec::new(),
147            unstaged: why.filter(|_| failed || memory.blocked()),
148        }
149    }
150
151    pub(crate) fn since(&self) -> Counters {
152        self.memory.counters().since(self.since)
153    }
154
155    pub(crate) fn keeps(&self, fork: bool, target: bool, offered: bool) -> bool {
156        self.memory.keeps(fork, target, offered)
157    }
158
159    pub(crate) fn mark_every(&self) -> usize {
160        self.memory.mark_every()
161    }
162
163    /// `slot` where a volatile parameter reaches it, `fork` where two values read it.
164    pub(crate) fn stamp(&self, slot: Option<Hash>, fork: bool, kind: PayloadKind) -> Stamp {
165        Stamp {
166            tree: self.tree,
167            fork,
168            slot: slot.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
169        }
170    }
171
172    pub(crate) fn note(
173        &mut self,
174        node: &str,
175        key: Hash,
176        kind: PayloadKind,
177        outcome: Outcome,
178    ) -> usize {
179        self.lookups.push(Lookup {
180            node: node.to_string(),
181            key,
182            kind,
183            outcome,
184            store: None,
185        })
186    }
187
188    /// Read unnoted; its value notes one lookup.
189    pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
190        self.memory.load(key, expected, stamp)
191    }
192
193    /// What a value computed, merged into what memory holds of it.
194    pub(crate) fn store(
195        &mut self,
196        (key, noted): (Hash, Option<usize>),
197        payload: Payload,
198        label: Option<&Label>,
199        stamp: Stamp,
200    ) {
201        let outcome = match self.memory.merge(key, payload, label, stamp) {
202            Kept::Held => Outcome::ComputedStored,
203            Kept::Replaced => Outcome::ComputedReplaced,
204            Kept::Refused => return,
205        };
206        let Some(lookup) = noted.and_then(|at| self.lookups.get_mut(at)) else {
207            return;
208        };
209        match lookup.outcome {
210            Outcome::ComputedNotStored => lookup.outcome = outcome,
211            Outcome::Hit if lookup.kind == PayloadKind::Run => lookup.outcome = Outcome::Extended,
212            _ => {}
213        }
214    }
215}