use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use std::time::Instant;
use kevy_store::Store;
#[derive(Default)]
pub(crate) struct ShardStats {
pub used_memory: AtomicU64,
pub used_memory_peak: AtomicU64,
pub keys: AtomicU64,
pub expires: AtomicU64,
pub expired_keys: AtomicU64,
pub evicted_keys: AtomicU64,
pub commands_processed: AtomicU64,
pub connections_received: AtomicU64,
}
static SLOTS: RwLock<Vec<Arc<ShardStats>>> = RwLock::new(Vec::new());
thread_local! {
static LOCAL: std::cell::RefCell<Option<Arc<ShardStats>>> =
const { std::cell::RefCell::new(None) };
static LOCAL_SHARD: std::cell::Cell<usize> = const { std::cell::Cell::new(usize::MAX) };
static LOCAL_CMDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
static LOCAL_CONNS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
fn slot(shard: usize) -> Arc<ShardStats> {
{
let r = SLOTS.read().expect("stats SLOTS poisoned");
if let Some(s) = r.get(shard) {
return s.clone();
}
}
let mut w = SLOTS.write().expect("stats SLOTS poisoned");
while w.len() <= shard {
w.push(Arc::new(ShardStats::default()));
}
w[shard].clone()
}
pub(crate) fn register_shard(shard: usize) {
let s = slot(shard);
LOCAL.with(|c| *c.borrow_mut() = Some(s));
LOCAL_SHARD.with(|c| c.set(shard));
}
fn with_local(f: impl FnOnce(&ShardStats)) {
LOCAL.with(|c| {
if let Some(s) = c.borrow().as_ref() {
f(s);
}
});
}
pub(crate) fn publish_gauges(store: &Store) {
let cmds = LOCAL_CMDS.with(|c| c.get());
let conns = LOCAL_CONNS.with(|c| c.get());
with_local(|s| {
s.used_memory.store(store.used_memory(), Relaxed);
s.used_memory_peak.store(store.used_memory_peak(), Relaxed);
s.keys.store(store.dbsize() as u64, Relaxed);
s.expires.store(store.expires_count() as u64, Relaxed);
s.expired_keys.store(store.expired_keys_total(), Relaxed);
s.evicted_keys.store(store.evictions_total(), Relaxed);
s.commands_processed.store(cmds, Relaxed);
s.connections_received.store(conns, Relaxed);
});
}
#[inline]
pub(crate) fn add_command() {
LOCAL_CMDS.with(|c| c.set(c.get().wrapping_add(1)));
}
#[inline]
pub(crate) fn add_connection() {
LOCAL_CONNS.with(|c| c.set(c.get().wrapping_add(1)));
}
#[derive(Default)]
pub(crate) struct Totals {
pub used_memory: u64,
pub used_memory_peak: u64,
pub keys: u64,
pub expires: u64,
pub expired_keys: u64,
pub evicted_keys: u64,
pub commands_processed: u64,
pub connections_received: u64,
}
pub(crate) fn aggregate() -> Totals {
let r = SLOTS.read().expect("stats SLOTS poisoned");
let mut t = Totals::default();
for s in r.iter() {
t.used_memory += s.used_memory.load(Relaxed);
t.used_memory_peak += s.used_memory_peak.load(Relaxed);
t.keys += s.keys.load(Relaxed);
t.expires += s.expires.load(Relaxed);
t.expired_keys += s.expired_keys.load(Relaxed);
t.evicted_keys += s.evicted_keys.load(Relaxed);
t.commands_processed += s.commands_processed.load(Relaxed);
t.connections_received += s.connections_received.load(Relaxed);
}
t
}
static OPS_RING: Mutex<Vec<(u128, u64)>> = Mutex::new(Vec::new());
static START: OnceLock<Instant> = OnceLock::new();
const OPS_WINDOW: usize = 16;
fn elapsed_ms() -> u128 {
START.get_or_init(Instant::now).elapsed().as_millis()
}
pub(crate) fn sample_ops_if_lead() {
if LOCAL_SHARD.with(|c| c.get()) != 0 {
return;
}
let total = aggregate().commands_processed;
let mut ring = OPS_RING.lock().expect("OPS_RING poisoned");
ring.push((elapsed_ms(), total));
if ring.len() > OPS_WINDOW {
let drop = ring.len() - OPS_WINDOW;
ring.drain(0..drop);
}
}
pub(crate) fn instantaneous_ops_per_sec(current: u64) -> u64 {
let ring = OPS_RING.lock().expect("OPS_RING poisoned");
let Some(&(oldest_ms, oldest_cmds)) = ring.first() else {
return 0;
};
let dt_ms = elapsed_ms().saturating_sub(oldest_ms);
if dt_ms == 0 {
return 0;
}
let dc = current.saturating_sub(oldest_cmds);
((dc as u128 * 1000) / dt_ms) as u64
}