Skip to main content

sva_engine/
cache.rs

1// Concern: declares what a store holds under a content hash and how a key is built | Non-concern: any one store's medium and budget (disk.rs, memory.rs) | IO: (Hash) -> a payload + traces
2
3mod disk;
4mod entry_bytes;
5mod evict;
6mod label;
7mod memory;
8mod pack;
9mod slots;
10mod stats;
11mod tiered;
12
13pub use disk::{DiskCache, ENGINE_DIR_PREFIX, IO_NANOS_PER_BYTE};
14pub use entry_bytes::{RawF64, SampleCodec};
15pub use memory::MemoryCache;
16pub use pack::{FORMAT as PACK_FORMAT, Medium, Pack, VecMedium};
17pub use slots::{DEFAULT_SLOT_BYTES, Put, Slots};
18pub(crate) use stats::Recording;
19pub use stats::{CacheStats, Lookup, Outcome};
20pub use sva_formula::Hash;
21pub use tiered::Tiered;
22
23use std::path::Path;
24use std::time::Duration;
25
26use sva_formula::SpectralSum;
27use sva_samples::{Buffer, FilterTrace, Frames, Label};
28
29/// A spectral sum, one collapse of it, or one analysis of that collapse.
30#[derive(Clone, Debug, PartialEq)]
31pub enum Payload {
32    Samples(Box<Buffer>),
33    Frames(Box<Frames>),
34    Symbolic(Box<SpectralSum>),
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum PayloadKind {
39    Samples,
40    Frames,
41    Symbolic,
42}
43
44/// An entry not matching this is a miss, never a coercion.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Expected {
47    Samples {
48        rate: u32,
49        width: usize,
50        samples: usize,
51    },
52    Frames,
53    Symbolic,
54}
55
56/// Where a hit was answered from: this process's heap, a store that outlives it, or a slot.
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub enum Tier {
59    Memory,
60    Persistent,
61    Volatile,
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub struct Entry {
66    pub payload: Payload,
67    pub traces: Vec<FilterTrace>,
68    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
69    pub label: Option<Label>,
70    pub tier: Tier,
71}
72
73impl Expected {
74    pub fn kind(self) -> PayloadKind {
75        match self {
76            Expected::Samples { .. } => PayloadKind::Samples,
77            Expected::Frames => PayloadKind::Frames,
78            Expected::Symbolic => PayloadKind::Symbolic,
79        }
80    }
81}
82
83impl Payload {
84    pub fn kind(&self) -> PayloadKind {
85        match self {
86            Payload::Samples(_) => PayloadKind::Samples,
87            Payload::Frames(_) => PayloadKind::Frames,
88            Payload::Symbolic(_) => PayloadKind::Symbolic,
89        }
90    }
91
92    pub fn samples(&self) -> Option<&Buffer> {
93        match self {
94            Payload::Samples(buffer) => Some(buffer),
95            _ => None,
96        }
97    }
98
99    pub fn symbolic(&self) -> Option<&SpectralSum> {
100        match self {
101            Payload::Symbolic(sum) => Some(sum),
102            _ => None,
103        }
104    }
105
106    pub fn bytes(&self) -> usize {
107        match self {
108            Payload::Samples(b) => b.len() * b.width * size_of::<f64>(),
109            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
110            Payload::Symbolic(n) => {
111                size_of::<SpectralSum>()
112                    + n.lanes
113                        .iter()
114                        .map(|lane| {
115                            lane.atoms.len() * size_of::<sva_formula::SpectralAtom>()
116                                + (lane.series.len() + lane.modal.len()) * size_of::<SpectralSum>()
117                        })
118                        .sum::<usize>()
119            }
120        }
121    }
122
123    pub fn answers(&self, expected: Expected) -> bool {
124        match (self, expected) {
125            (
126                Payload::Samples(b),
127                Expected::Samples {
128                    rate,
129                    width,
130                    samples,
131                },
132            ) => b.rate == rate && b.width == width && b.len() == samples,
133            (Payload::Frames(_), Expected::Frames) => true,
134            (Payload::Symbolic(_), Expected::Symbolic) => true,
135            _ => false,
136        }
137    }
138}
139
140/// What a render spent on one value. `wasm32-unknown-unknown` has no clock at all, so a
141/// render there measures nothing.
142#[derive(Clone, Copy)]
143pub struct Cost {
144    #[cfg(not(target_arch = "wasm32"))]
145    began: std::time::Instant,
146}
147
148impl Cost {
149    pub fn begun() -> Cost {
150        Cost {
151            #[cfg(not(target_arch = "wasm32"))]
152            began: std::time::Instant::now(),
153        }
154    }
155
156    pub fn elapsed(self) -> Duration {
157        #[cfg(not(target_arch = "wasm32"))]
158        return self.began.elapsed();
159        #[cfg(target_arch = "wasm32")]
160        return Duration::ZERO;
161    }
162}
163
164/// A hash carries the table version, so a bump retires every symbolic entry.
165pub fn symbolic_key(src: Hash) -> Hash {
166    mixed(src, &[0x73_79_6d_62_6f_6c_69_63])
167}
168
169/// The buffer's own key and the window it was read through.
170pub fn frames_key(buffer: Hash, window: usize, hop: usize) -> Hash {
171    mixed(
172        buffer,
173        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
174    )
175}
176
177/// One closed form at one rate, origin, length and width, scored or not: the label is part of
178/// the value.
179pub fn buffer_key(
180    symbolic: Hash,
181    rate: u32,
182    origin_secs: f64,
183    samples: usize,
184    width: usize,
185    score: sva_samples::AliasScore,
186) -> Hash {
187    mixed(
188        symbolic,
189        &[
190            u64::from(rate),
191            origin_secs.to_bits(),
192            samples as u64,
193            width as u64,
194            u64::from(score == sva_samples::AliasScore::Asked),
195            0x62_75_66_66_65_72_00_01,
196        ],
197    )
198}
199
200fn renamed(traces: &[FilterTrace], node: &str) -> Vec<FilterTrace> {
201    traces
202        .iter()
203        .map(|t| FilterTrace {
204            node: node.to_string(),
205            ..t.clone()
206        })
207        .collect()
208}
209
210const ADDRESS_ROTATE: u32 = 17;
211
212pub(crate) fn mixed(seed: Hash, parts: &[u64]) -> Hash {
213    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
214    for part in parts {
215        lanes.word(*part);
216    }
217    lanes.finish()
218}
219
220pub trait Cache: Sync {
221    fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry>;
222
223    /// `load` with no side effect: no recency, promotion, fault or removal.
224    fn peek(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry>;
225
226    fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>);
227
228    fn holds(&self, key: Hash) -> bool;
229
230    fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool;
231
232    fn sweep(&self);
233
234    fn held_bytes(&self) -> u64;
235
236    fn evicted_bytes(&self) -> u64;
237
238    fn faults(&self) -> u64 {
239        0
240    }
241
242    fn max_bytes(&self) -> u64;
243
244    fn dir(&self) -> Option<&Path> {
245        None
246    }
247}