v0.34.0_1
This commit is contained in:
1 parent
9cfc4588cc
commit
f20c8da718
25 files changed
+694
-117
No files matched your search
Vendored
+187
@@ -0,0 +1,187 @@
|
||||
// Lock-free read path for bounded, per-domain cache dashboard counters.
|
||||
//
|
||||
// Cache-disabled proxy requests still contribute to BYPASS and reason totals,
|
||||
// but must never lock the HOT object/LRU store. Published domain/reason indexes
|
||||
// are immutable; individual counters are atomics. Only first sight of a new
|
||||
// domain/reason acquires the registration mutex.
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
const DOMAIN_STATS_LIMIT: usize = 4096;
|
||||
const FAST_REASONS: [&str; 4] = ["cache_disabled", "cache_hit", "cache_miss", "request_cookie"];
|
||||
|
||||
type ReasonIndex = BTreeMap<&'static str, Arc<AtomicU64>>;
|
||||
type DomainIndex = BTreeMap<String, BTreeMap<String, Arc<DomainStats>>>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct PublishedDomains {
|
||||
index: DomainIndex,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
struct DomainStats {
|
||||
hits: AtomicU64,
|
||||
misses: AtomicU64,
|
||||
bypasses: AtomicU64,
|
||||
stored: AtomicU64,
|
||||
saved_body_bytes: AtomicU64,
|
||||
fast_reasons: [AtomicU64; FAST_REASONS.len()],
|
||||
reasons: ArcSwap<ReasonIndex>,
|
||||
register_reason: Mutex<()>,
|
||||
}
|
||||
|
||||
impl Default for DomainStats {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hits: AtomicU64::new(0),
|
||||
misses: AtomicU64::new(0),
|
||||
bypasses: AtomicU64::new(0),
|
||||
stored: AtomicU64::new(0),
|
||||
saved_body_bytes: AtomicU64::new(0),
|
||||
fast_reasons: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||
reasons: ArcSwap::from_pointee(ReasonIndex::new()),
|
||||
register_reason: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DomainStats {
|
||||
fn record(&self, outcome: &str, reason: &'static str) {
|
||||
match outcome {
|
||||
"HIT" => &self.hits,
|
||||
"MISS" => &self.misses,
|
||||
_ => &self.bypasses,
|
||||
}.fetch_add(1, Ordering::Relaxed);
|
||||
self.record_reason(reason);
|
||||
}
|
||||
|
||||
fn record_reason(&self, reason: &'static str) {
|
||||
// Cache OFF is the highest-volume case; avoid reason map lookup and
|
||||
// any shared lock/allocator activity for its dashboard reason.
|
||||
if let Some(index) = FAST_REASONS.iter().position(|label| *label == reason) {
|
||||
self.fast_reasons[index].fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
let published = self.reasons.load();
|
||||
if let Some(counter) = published.get(reason) {
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
drop(published);
|
||||
let _guard = self.register_reason.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let published = self.reasons.load_full();
|
||||
if let Some(counter) = published.get(reason) {
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
let mut next = (*published).clone();
|
||||
next.insert(reason, Arc::new(AtomicU64::new(1)));
|
||||
self.reasons.store(Arc::new(next));
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> DomainCounters {
|
||||
let load = |value: &AtomicU64| value.load(Ordering::Relaxed);
|
||||
let mut reasons = BTreeMap::new();
|
||||
for (label, counter) in FAST_REASONS.iter().zip(&self.fast_reasons) {
|
||||
let value = load(counter);
|
||||
if value > 0 {
|
||||
reasons.insert((*label).to_owned(), value);
|
||||
}
|
||||
}
|
||||
for (label, counter) in self.reasons.load().iter() {
|
||||
reasons.insert((*label).to_owned(), load(counter));
|
||||
}
|
||||
DomainCounters {
|
||||
hits: load(&self.hits),
|
||||
misses: load(&self.misses),
|
||||
bypasses: load(&self.bypasses),
|
||||
stored: load(&self.stored),
|
||||
saved_body_bytes: load(&self.saved_body_bytes),
|
||||
reasons,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DomainStatsStore {
|
||||
domains: ArcSwap<PublishedDomains>,
|
||||
register: Mutex<()>,
|
||||
}
|
||||
|
||||
impl Default for DomainStatsStore {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
domains: ArcSwap::from_pointee(PublishedDomains::default()),
|
||||
register: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DomainStatsStore {
|
||||
fn with(&self, scope: &str, domain: &str, action: impl FnOnce(&DomainStats)) {
|
||||
let published = self.domains.load();
|
||||
if let Some(counters) = published.index.get(scope).and_then(|hosts| hosts.get(domain)) {
|
||||
action(counters);
|
||||
return;
|
||||
}
|
||||
drop(published);
|
||||
// Rare path: register only 4096 distinct (scope, domain) pairs. The
|
||||
// published map is cloned on first insertion, never on a request hit.
|
||||
let _guard = self.register.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let published = self.domains.load_full();
|
||||
if let Some(counters) = published.index.get(scope).and_then(|hosts| hosts.get(domain)) {
|
||||
action(counters);
|
||||
return;
|
||||
}
|
||||
if published.count >= DOMAIN_STATS_LIMIT {
|
||||
return;
|
||||
}
|
||||
let mut next = (*published).clone();
|
||||
let counters = Arc::new(DomainStats::default());
|
||||
next.index.entry(scope.to_owned()).or_default()
|
||||
.insert(domain.to_owned(), Arc::clone(&counters));
|
||||
next.count += 1;
|
||||
self.domains.store(Arc::new(next));
|
||||
action(&counters);
|
||||
}
|
||||
|
||||
fn record(&self, scope: &str, domain: &str, outcome: &str, reason: &'static str) {
|
||||
self.with(scope, domain, |counters| counters.record(outcome, reason));
|
||||
}
|
||||
|
||||
fn record_reason(&self, scope: &str, domain: &str, reason: &'static str) {
|
||||
self.with(scope, domain, |counters| counters.record_reason(reason));
|
||||
}
|
||||
|
||||
fn stored(&self, scope: &str, domain: &str) {
|
||||
self.with(scope, domain, |counters| {
|
||||
counters.stored.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
fn saved_body_hit(&self, scope: &str, domain: &str, bytes: usize) {
|
||||
// COLD entries loaded from persisted metadata may be served before a
|
||||
// domain is registered. Preserve first-hit byte accounting as well.
|
||||
self.with(scope, domain, |counters| {
|
||||
counters.saved_body_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
let _guard = self.register.lock().unwrap_or_else(|e| e.into_inner());
|
||||
self.domains.store(Arc::new(PublishedDomains::default()));
|
||||
}
|
||||
|
||||
fn snapshot(&self, scope: Option<&str>) -> BTreeMap<(String, String), DomainCounters> {
|
||||
let published = self.domains.load();
|
||||
let mut counters = BTreeMap::new();
|
||||
for (vhost, domains) in &published.index {
|
||||
if !scope.is_none_or(|selected| selected == vhost) {
|
||||
continue;
|
||||
}
|
||||
for (domain, metric) in domains {
|
||||
counters.insert((vhost.clone(), domain.clone()), metric.snapshot());
|
||||
}
|
||||
}
|
||||
counters
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user