use std::sync::Arc;
pub(crate) const SERIES_CACHE_SIZE: usize = 16;
pub(crate) trait CacheValue {
type Strong;
fn upgrade(&self) -> Option<Self::Strong>;
fn is_valid(strong: &Self::Strong) -> bool;
}
pub(crate) struct LabelCacheEntry<T> {
metric_id: usize,
fingerprint: u64,
ordered_labels: Vec<(String, String)>,
value: T,
}
impl<T> LabelCacheEntry<T> {
fn matches_ordered(&self, metric_id: usize, labels: &[(&str, &str)]) -> bool {
if self.metric_id != metric_id || self.ordered_labels.len() != labels.len() {
return false;
}
labels.iter().enumerate().all(|(idx, (k, v))| {
let (ek, ev) = &self.ordered_labels[idx];
ek == k && ev == v
})
}
fn matches(&self, metric_id: usize, fingerprint: u64, labels: &[(&str, &str)]) -> bool {
if self.metric_id != metric_id
|| self.fingerprint != fingerprint
|| self.ordered_labels.len() != labels.len()
{
return false;
}
labels.iter().enumerate().all(|(idx, (k, v))| {
let (ek, ev) = &self.ordered_labels[idx];
ek == k && ev == v
})
}
}
pub(crate) struct LabelCache<T, const N: usize> {
last: Option<usize>,
entries: [Option<LabelCacheEntry<T>>; N],
}
impl<T, const N: usize> LabelCache<T, N>
where
T: CacheValue,
{
pub(crate) fn new() -> Self {
debug_assert!(N.is_power_of_two());
Self {
last: None,
entries: std::array::from_fn(|_| None),
}
}
pub(crate) fn get(
&mut self,
metric_id: usize,
labels: &[(&str, &str)],
) -> Option<<T as CacheValue>::Strong> {
if let Some(index) = self.last {
if let Some(value) = self.get_at(index, metric_id, labels) {
return Some(value);
}
}
let fingerprint = label_fingerprint(labels);
let index = cache_index::<N>(fingerprint);
let value = self.get_at_with_fingerprint(index, metric_id, fingerprint, labels)?;
self.last = Some(index);
Some(value)
}
pub(crate) fn insert(&mut self, metric_id: usize, labels: &[(&str, &str)], value: T) {
let fingerprint = label_fingerprint(labels);
let index = cache_index::<N>(fingerprint);
let ordered_labels = labels
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect();
self.entries[index] = Some(LabelCacheEntry {
metric_id,
fingerprint,
ordered_labels,
value,
});
self.last = Some(index);
}
fn get_at(
&self,
index: usize,
metric_id: usize,
labels: &[(&str, &str)],
) -> Option<<T as CacheValue>::Strong> {
let entry = self.entries[index].as_ref()?;
if !entry.matches_ordered(metric_id, labels) {
return None;
}
let strong = entry.value.upgrade()?;
if !T::is_valid(&strong) {
return None;
}
Some(strong)
}
fn get_at_with_fingerprint(
&self,
index: usize,
metric_id: usize,
fingerprint: u64,
labels: &[(&str, &str)],
) -> Option<<T as CacheValue>::Strong> {
let entry = self.entries[index].as_ref()?;
if !entry.matches(metric_id, fingerprint, labels) {
return None;
}
let strong = entry.value.upgrade()?;
if !T::is_valid(&strong) {
return None;
}
Some(strong)
}
}
fn cache_index<const N: usize>(fingerprint: u64) -> usize {
debug_assert!(N.is_power_of_two());
(fingerprint as usize) & (N - 1)
}
pub(crate) fn label_fingerprint(labels: &[(&str, &str)]) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut hash = FNV_OFFSET;
for (k, v) in labels {
for byte in k.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash ^= 0xff;
hash = hash.wrapping_mul(FNV_PRIME);
for byte in v.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash ^= 0xfe;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
impl<T> CacheValue for std::sync::Weak<T>
where
T: CacheableSeries,
{
type Strong = Arc<T>;
fn upgrade(&self) -> Option<Self::Strong> {
self.upgrade()
}
fn is_valid(strong: &Self::Strong) -> bool {
!strong.is_evicted()
}
}
pub(crate) trait CacheableSeries {
fn is_evicted(&self) -> bool;
}