use std::sync::atomic::{AtomicU64, Ordering};
use crate::changelog::CommitId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScopeDigestOutcome {
Pruned,
LoadedPresent,
LoadedOpaque,
LoadedAbsent,
Unconstrained,
}
static PRUNED: AtomicU64 = AtomicU64::new(0);
static LOADED_PRESENT: AtomicU64 = AtomicU64::new(0);
static LOADED_OPAQUE: AtomicU64 = AtomicU64::new(0);
static LOADED_ABSENT: AtomicU64 = AtomicU64::new(0);
static UNCONSTRAINED: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
pub(crate) mod by_projection {
use super::ScopeDigestOutcome;
use std::collections::BTreeMap;
use std::sync::{Mutex, OnceLock};
type Breakdown = BTreeMap<String, BTreeMap<&'static str, u64>>;
fn table() -> &'static Mutex<Breakdown> {
static TABLE: OnceLock<Mutex<Breakdown>> = OnceLock::new();
TABLE.get_or_init(|| Mutex::new(BTreeMap::new()))
}
pub(crate) fn record(schema_keys: &[String], outcome: ScopeDigestOutcome) {
let label = if schema_keys.is_empty() {
"<unconstrained>".to_string()
} else {
schema_keys.join("+")
};
let bucket = match outcome {
ScopeDigestOutcome::Pruned => "pruned",
ScopeDigestOutcome::LoadedPresent => "loaded_present",
ScopeDigestOutcome::LoadedOpaque => "loaded_opaque",
ScopeDigestOutcome::LoadedAbsent => "loaded_absent",
ScopeDigestOutcome::Unconstrained => "unconstrained",
};
let mut table = table().lock().expect("projection census is not poisoned");
*table.entry(label).or_default().entry(bucket).or_insert(0) += 1;
}
pub(crate) fn take() -> Breakdown {
std::mem::take(&mut *table().lock().expect("projection census is not poisoned"))
}
}
pub(crate) fn record_scope_digest_outcome(outcome: ScopeDigestOutcome) {
let counter = match outcome {
ScopeDigestOutcome::Pruned => &PRUNED,
ScopeDigestOutcome::LoadedPresent => &LOADED_PRESENT,
ScopeDigestOutcome::LoadedOpaque => &LOADED_OPAQUE,
ScopeDigestOutcome::LoadedAbsent => &LOADED_ABSENT,
ScopeDigestOutcome::Unconstrained => &UNCONSTRAINED,
};
counter.fetch_add(1, Ordering::Relaxed);
#[cfg(test)]
THREAD_CENSUS.with(|slot| {
let mut census = slot.get();
census.record(outcome);
slot.set(census);
});
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ScopeDigestCensus {
pub(crate) pruned: u64,
pub(crate) loaded_present: u64,
pub(crate) loaded_opaque: u64,
pub(crate) loaded_absent: u64,
pub(crate) unconstrained: u64,
}
#[cfg(test)]
thread_local! {
static THREAD_CENSUS: std::cell::Cell<ScopeDigestCensus> =
const { std::cell::Cell::new(ScopeDigestCensus::empty()) };
}
#[cfg(test)]
pub(crate) fn reset_thread_scope_digest_census() {
THREAD_CENSUS.with(|census| census.set(ScopeDigestCensus::empty()));
}
#[cfg(test)]
pub(crate) fn thread_scope_digest_census() -> ScopeDigestCensus {
THREAD_CENSUS.with(std::cell::Cell::get)
}
pub(crate) fn scope_digest_census() -> ScopeDigestCensus {
ScopeDigestCensus {
pruned: PRUNED.load(Ordering::Relaxed),
loaded_present: LOADED_PRESENT.load(Ordering::Relaxed),
loaded_opaque: LOADED_OPAQUE.load(Ordering::Relaxed),
loaded_absent: LOADED_ABSENT.load(Ordering::Relaxed),
unconstrained: UNCONSTRAINED.load(Ordering::Relaxed),
}
}
impl ScopeDigestCensus {
const fn empty() -> Self {
Self {
pruned: 0,
loaded_present: 0,
loaded_opaque: 0,
loaded_absent: 0,
unconstrained: 0,
}
}
#[cfg(test)]
fn record(&mut self, outcome: ScopeDigestOutcome) {
let counter = match outcome {
ScopeDigestOutcome::Pruned => &mut self.pruned,
ScopeDigestOutcome::LoadedPresent => &mut self.loaded_present,
ScopeDigestOutcome::LoadedOpaque => &mut self.loaded_opaque,
ScopeDigestOutcome::LoadedAbsent => &mut self.loaded_absent,
ScopeDigestOutcome::Unconstrained => &mut self.unconstrained,
};
*counter = counter.saturating_add(1);
}
pub(crate) fn probed(&self) -> u64 {
self.pruned + self.loaded_present + self.loaded_opaque + self.loaded_absent
}
pub(crate) fn since(&self, earlier: &Self) -> Self {
Self {
pruned: self.pruned.saturating_sub(earlier.pruned),
loaded_present: self.loaded_present.saturating_sub(earlier.loaded_present),
loaded_opaque: self.loaded_opaque.saturating_sub(earlier.loaded_opaque),
loaded_absent: self.loaded_absent.saturating_sub(earlier.loaded_absent),
unconstrained: self.unconstrained.saturating_sub(earlier.unconstrained),
}
}
pub(crate) fn emit(&self, start_commit_id: &CommitId) {
if self.probed() == 0 && self.unconstrained == 0 {
return;
}
tracing::debug!(
target: "lix_perf",
start_commit_id = %start_commit_id,
pruned = self.pruned,
loaded_present = self.loaded_present,
loaded_opaque = self.loaded_opaque,
loaded_absent = self.loaded_absent,
unconstrained = self.unconstrained,
"lix.commit_graph.touched_scope_digest",
);
}
}