use sva_formula::Hash;
use sva_samples::Label;
use super::memory::{Counters, Kept, Memory, Stamp};
use super::{Entry, Expected, Payload, PayloadKind};
use crate::recent::Recent;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Hit,
ComputedStored,
ComputedNotStored,
ComputedReplaced,
Extended,
Prefix,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Lookup {
pub node: String,
pub key: Hash,
pub kind: PayloadKind,
pub outcome: Outcome,
pub store: Option<bool>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CacheStats {
pub lookups: Vec<Lookup>,
pub shed: usize,
pub bytes: u64,
pub max_bytes: u64,
pub entries: usize,
pub evictions: u64,
pub tier: Counters,
pub reached: Vec<(i64, usize)>,
pub typed: Vec<String>,
pub planned: Vec<String>,
pub unstaged: Option<String>,
}
impl CacheStats {
pub fn nodes(&self) -> usize {
let mut names: Vec<&str> = self.lookups.iter().map(|l| l.node.as_str()).collect();
names.sort_unstable();
names.dedup();
names.len()
}
pub fn hits(&self) -> usize {
self.lookups
.iter()
.filter(|l| l.outcome == Outcome::Hit)
.count()
}
pub fn computed(&self) -> usize {
self.count(|o| o != Outcome::Hit)
}
pub fn stored(&self) -> usize {
self.count(|o| o == Outcome::ComputedStored)
}
pub fn replaced(&self) -> usize {
self.count(|o| o == Outcome::ComputedReplaced)
}
pub fn extended(&self) -> usize {
self.count(|o| o == Outcome::Extended)
}
fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
let values = self.lookups.iter().filter(|l| l.store.is_none());
values.filter(|l| of(l.outcome)).count()
}
}
pub(crate) struct Recording {
memory: Memory,
tree: u64,
since: Counters,
failures: u64,
lookups: Recent<Lookup>,
reached: Option<Vec<(i64, usize)>>,
}
impl Recording {
pub(crate) fn over(memory: &Memory) -> Recording {
Recording {
memory: memory.clone(),
tree: memory.begin_tree(),
since: memory.counters(),
failures: memory.failures().0,
lookups: Recent::keeping(usize::MAX),
reached: Some(Vec::new()),
}
}
pub(crate) fn latest(self, kept: usize) -> Recording {
Recording {
lookups: Recent::keeping(kept),
reached: None,
..self
}
}
pub(crate) fn found(&mut self, lookups: Vec<Lookup>) {
for lookup in lookups {
self.lookups.push(lookup);
}
}
pub(crate) fn reach(&mut self, at: i64) {
let made = self.lookups.made();
if let Some(reached) = &mut self.reached {
reached.push((at, made));
}
}
pub(crate) fn stats(&self) -> CacheStats {
let memory = &self.memory;
let tier = memory.counters().since(self.since);
let (failures, why) = memory.failures();
let failed = failures > self.failures;
CacheStats {
lookups: self.lookups.iter().cloned().collect(),
shed: self.lookups.shed(),
bytes: memory.bytes(),
max_bytes: memory.max_bytes(),
entries: memory.entries(),
evictions: tier.evictions,
tier,
reached: self.reached.clone().unwrap_or_default(),
typed: Vec::new(),
planned: Vec::new(),
unstaged: why.filter(|_| failed || memory.blocked()),
}
}
pub(crate) fn since(&self) -> Counters {
self.memory.counters().since(self.since)
}
pub(crate) fn keeps(&self, fork: bool, target: bool, offered: bool) -> bool {
self.memory.keeps(fork, target, offered)
}
pub(crate) fn mark_every(&self) -> usize {
self.memory.mark_every()
}
pub(crate) fn stamp(&self, slot: Option<Hash>, fork: bool, kind: PayloadKind) -> Stamp {
Stamp {
tree: self.tree,
fork,
slot: slot.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
}
}
pub(crate) fn note(
&mut self,
node: &str,
key: Hash,
kind: PayloadKind,
outcome: Outcome,
) -> usize {
self.lookups.push(Lookup {
node: node.to_string(),
key,
kind,
outcome,
store: None,
})
}
pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
self.memory.load(key, expected, stamp)
}
pub(crate) fn store(
&mut self,
(key, noted): (Hash, Option<usize>),
payload: Payload,
label: Option<&Label>,
stamp: Stamp,
) {
let outcome = match self.memory.merge(key, payload, label, stamp) {
Kept::Held => Outcome::ComputedStored,
Kept::Replaced => Outcome::ComputedReplaced,
Kept::Refused => return,
};
let Some(lookup) = noted.and_then(|at| self.lookups.get_mut(at)) else {
return;
};
match lookup.outcome {
Outcome::ComputedNotStored => lookup.outcome = outcome,
Outcome::Hit if lookup.kind == PayloadKind::Run => lookup.outcome = Outcome::Extended,
_ => {}
}
}
}