Skip to main content

sva_engine/
cache.rs

1// Concern: declares what memory and a disk hold under a content hash and how a value's key is built | Non-concern: what memory keeps and evicts (memory.rs) | IO: (Hash) -> a payload
2
3mod codec;
4mod index;
5pub(crate) mod log;
6mod memory;
7mod persist;
8mod stats;
9mod stored;
10mod tier;
11
12pub use codec::STORE_FORMAT;
13pub use memory::{CachePolicy, Counters, DEFAULT_CACHE_BYTES, DEFAULT_MARK_EVERY, PrunePolicy};
14pub(crate) use memory::{Known, Memory, Offered};
15pub use persist::{Backend, DEFAULT_STORE_BYTES, INDEX_NAME, Persisted, Store};
16pub(crate) use stats::Recording;
17pub use stats::{CacheStats, Lookup, Outcome};
18pub use stored::Stored;
19pub(crate) use stored::node_key;
20pub use sva_formula::Hash;
21pub(crate) use tier::now;
22pub use tier::{FETCH_READS, Nothing, Tier};
23
24use std::collections::BTreeMap;
25use std::sync::Arc;
26
27use sva_samples::{Buffer, Frames, Label, MachineState};
28
29/// A value's segments, a stateful value's run, or one analysis of a value, each shared: a clone
30/// hands out the same samples, never a copy of them.
31#[derive(Clone, Debug, PartialEq)]
32pub enum Payload {
33    Segments(Vec<Arc<Buffer>>),
34    Frames(Arc<Frames>),
35    Run(Arc<Run>),
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum PayloadKind {
40    Segments,
41    Frames,
42    Run,
43}
44
45/// One segment of a run: samples, marked states, and the segment before it.
46#[derive(Clone)]
47pub struct Run {
48    pub samples: Arc<Buffer>,
49    pub marks: BTreeMap<i64, MachineState>,
50    pub parent: Option<Hash>,
51}
52
53impl Run {
54    pub fn end(&self) -> i64 {
55        self.samples.extent().end
56    }
57
58    pub fn bytes(&self) -> usize {
59        let marks: usize = self.marks.values().map(MachineState::bytes).sum();
60        self.samples.len() * self.samples.width * size_of::<f64>() + marks
61    }
62}
63
64/// The state is the node's own where its samples end, so the samples decide.
65impl PartialEq for Run {
66    fn eq(&self, other: &Run) -> bool {
67        self.samples == other.samples
68    }
69}
70
71impl std::fmt::Debug for Run {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("Run")
74            .field("samples", &self.samples.extent())
75            .finish()
76    }
77}
78
79/// An entry not matching this is a miss, never a coercion.
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum Expected {
82    Segments { rate: u32, width: usize },
83    Frames,
84    Run { rate: u32, width: usize },
85}
86
87#[derive(Clone, Debug, PartialEq)]
88pub struct Entry {
89    pub payload: Payload,
90    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
91    pub label: Option<Label>,
92}
93
94impl Payload {
95    pub fn run(self) -> Option<Arc<Run>> {
96        match self {
97            Payload::Run(run) => Some(run),
98            _ => None,
99        }
100    }
101
102    pub fn bytes(&self) -> usize {
103        match self {
104            Payload::Segments(parts) => parts
105                .iter()
106                .map(|b| b.len() * b.width * size_of::<f64>())
107                .sum(),
108            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
109            Payload::Run(run) => run.bytes(),
110        }
111    }
112
113    pub fn answers(&self, expected: Expected) -> bool {
114        match (self, expected) {
115            (Payload::Segments(parts), Expected::Segments { rate, width }) => {
116                parts.iter().all(|b| b.rate == rate && b.width == width)
117            }
118            (Payload::Frames(_), Expected::Frames) => true,
119            (Payload::Run(run), Expected::Run { rate, width }) => {
120                run.samples.rate == rate && run.samples.width == width
121            }
122            _ => false,
123        }
124    }
125}
126
127pub fn frames_key(value: Hash, window: usize, hop: usize) -> Hash {
128    mixed(
129        value,
130        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
131    )
132}
133
134/// One value at one rate, width and profile, never where a reader places it.
135pub fn value_key(
136    identity: Hash,
137    step: (i128, i128),
138    rate: u32,
139    width: usize,
140    profile: &sva_samples::Profile,
141) -> Hash {
142    mixed(
143        identity,
144        &[
145            step.0 as u64,
146            step.1 as u64,
147            u64::from(rate),
148            width as u64,
149            profile.precision_bits as u64,
150            profile.ceiling_hz.to_bits(),
151            profile.prune_db.to_bits(),
152            0x76_61_6c_75_65_00_00_01,
153        ],
154    )
155}
156
157const ADDRESS_ROTATE: u32 = 17;
158
159pub(crate) fn mixed(seed: Hash, parts: &[u64]) -> Hash {
160    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
161    for part in parts {
162        lanes.word(*part);
163    }
164    lanes.finish()
165}
166
167/// `more` laid among `parts`, each touching pair joined into one; a shared part is copied only
168/// to grow.
169pub(crate) fn joined(parts: &mut Vec<Arc<Buffer>>, more: Vec<Arc<Buffer>>) {
170    for part in more {
171        parts.push(part);
172    }
173    parts.sort_by_key(|b| b.start);
174    let mut out: Vec<Arc<Buffer>> = Vec::with_capacity(parts.len());
175    for part in parts.drain(..) {
176        match out.last_mut() {
177            Some(last) if last.extent().end >= part.start => {
178                let from = (last.extent().end - part.start) as usize;
179                if from < part.len() {
180                    let last = Arc::make_mut(last);
181                    for (held, more) in last.planes.iter_mut().zip(&part.planes) {
182                        held.extend_from_slice(&more[from.min(more.len())..]);
183                    }
184                }
185            }
186            _ => out.push(part),
187        }
188    }
189    *parts = out;
190}