use std::sync::{Mutex, MutexGuard, PoisonError};
use sva_formula::Hash;
use sva_samples::Label;
use super::store::{Kept, Stamp};
use super::{Cache, CachePolicy, Entry, Expected, Payload, PayloadKind};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Hit,
ComputedStored,
ComputedNotStored,
ComputedReplaced,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Lookup {
pub node: String,
pub key: Hash,
pub kind: PayloadKind,
pub outcome: Outcome,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CacheStats {
pub lookups: Vec<Lookup>,
pub bytes: u64,
pub max_bytes: u64,
pub entries: usize,
pub evictions: u64,
}
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.count(|o| o == Outcome::Hit)
}
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)
}
fn count(&self, of: impl Fn(Outcome) -> bool) -> usize {
self.lookups.iter().filter(|l| of(l.outcome)).count()
}
}
pub(crate) struct Recording<'a> {
cache: &'a Cache,
policy: CachePolicy,
tree: u64,
evictions: u64,
lookups: Mutex<Vec<Lookup>>,
}
impl<'a> Recording<'a> {
pub(crate) fn over(cache: &'a Cache, policy: Option<CachePolicy>) -> Recording<'a> {
Recording {
cache,
policy: policy.unwrap_or_else(|| cache.policy()),
tree: cache.begin_tree(),
evictions: cache.evictions(),
lookups: Mutex::new(Vec::new()),
}
}
pub(crate) fn finish(self) -> CacheStats {
CacheStats {
lookups: self
.lookups
.into_inner()
.unwrap_or_else(PoisonError::into_inner),
bytes: self.cache.bytes(),
max_bytes: self.cache.max_bytes(),
entries: self.cache.entries(),
evictions: self.cache.evictions() - self.evictions,
}
}
pub(crate) fn at(&self, slot: Option<Hash>, fork: bool, target: bool) -> Lens<'_> {
Lens {
recording: self,
slot,
fork,
stores: self.stores(fork, target),
}
}
pub(crate) fn stores(&self, fork: bool, target: bool) -> bool {
self.policy.stores(fork, target)
}
pub(crate) fn holds(&self, key: Hash) -> bool {
self.cache.holds(key)
}
fn held(&self) -> MutexGuard<'_, Vec<Lookup>> {
self.lookups.lock().unwrap_or_else(PoisonError::into_inner)
}
fn settle(&self, key: Hash, outcome: Outcome) {
if let Some(missed) = self
.held()
.iter_mut()
.rev()
.find(|l| l.key == key && l.outcome == Outcome::ComputedNotStored)
{
missed.outcome = outcome;
}
}
}
pub(crate) struct Lens<'a> {
recording: &'a Recording<'a>,
slot: Option<Hash>,
fork: bool,
stores: bool,
}
impl Lens<'_> {
fn stamp(&self, kind: PayloadKind) -> Stamp {
Stamp {
tree: self.recording.tree,
fork: self.fork,
slot: self
.slot
.map(|slot| super::mixed(slot, &[kind as u64, 0x73_6c_6f_74])),
}
}
pub(crate) fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry> {
let found = self
.recording
.cache
.load(key, expected, self.stamp(expected.kind()));
self.recording.held().push(Lookup {
node: node.to_string(),
key,
kind: expected.kind(),
outcome: match found {
Some(_) => Outcome::Hit,
None => Outcome::ComputedNotStored,
},
});
found
}
pub(crate) fn store(&self, key: Hash, payload: &Payload, label: Option<&Label>) {
if !self.stores {
return;
}
let stamp = self.stamp(payload.kind());
match self.recording.cache.store(key, payload, label, stamp) {
Kept::Held => self.recording.settle(key, Outcome::ComputedStored),
Kept::Replaced => self.recording.settle(key, Outcome::ComputedReplaced),
Kept::Refused => {}
}
}
}