use std::{
collections::HashMap,
sync::{
OnceLock,
atomic::{AtomicU64, Ordering},
},
};
use reovim_arch::sync::RwLock;
pub struct Counter {
value: AtomicU64,
}
impl Counter {
#[must_use]
pub const fn new() -> Self {
Self {
value: AtomicU64::new(0),
}
}
pub fn increment(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
pub fn add(&self, n: u64) {
self.value.fetch_add(n, Ordering::Relaxed);
}
#[must_use]
pub fn get(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn reset(&self) {
self.value.store(0, Ordering::Relaxed);
}
}
impl Default for Counter {
fn default() -> Self {
Self::new()
}
}
pub struct Histogram {
buckets: [AtomicU64; 16],
sum: AtomicU64,
count: AtomicU64,
}
impl Histogram {
#[must_use]
pub const fn new() -> Self {
Self {
buckets: [
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
AtomicU64::new(0),
],
sum: AtomicU64::new(0),
count: AtomicU64::new(0),
}
}
pub fn record(&self, value: u64) {
let bucket = if value == 0 {
0
} else {
(64 - value.leading_zeros()).min(15) as usize
};
self.buckets[bucket].fetch_add(1, Ordering::Relaxed);
self.sum.fetch_add(value, Ordering::Relaxed);
self.count.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
#[allow(clippy::cast_precision_loss)] pub fn mean(&self) -> f64 {
let count = self.count.load(Ordering::Relaxed);
if count == 0 {
0.0
} else {
self.sum.load(Ordering::Relaxed) as f64 / count as f64
}
}
#[must_use]
pub fn count(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
#[must_use]
pub fn sum(&self) -> u64 {
self.sum.load(Ordering::Relaxed)
}
#[must_use]
pub fn buckets(&self) -> [u64; 16] {
let mut result = [0u64; 16];
for (i, bucket) in self.buckets.iter().enumerate() {
result[i] = bucket.load(Ordering::Relaxed);
}
result
}
pub fn reset(&self) {
for bucket in &self.buckets {
bucket.store(0, Ordering::Relaxed);
}
self.sum.store(0, Ordering::Relaxed);
self.count.store(0, Ordering::Relaxed);
}
}
impl Default for Histogram {
fn default() -> Self {
Self::new()
}
}
pub struct MetricsRegistry {
counters: RwLock<HashMap<&'static str, std::sync::Arc<Counter>>>,
histograms: RwLock<HashMap<&'static str, std::sync::Arc<Histogram>>>,
}
impl MetricsRegistry {
#[must_use]
pub fn new() -> Self {
Self {
counters: RwLock::new(HashMap::new()),
histograms: RwLock::new(HashMap::new()),
}
}
#[must_use]
pub fn counter(&self, name: &'static str) -> std::sync::Arc<Counter> {
{
let counters = self.counters.read();
if let Some(counter) = counters.get(name) {
return counter.clone();
}
}
let mut counters = self.counters.write();
counters
.entry(name)
.or_insert_with(|| std::sync::Arc::new(Counter::new()))
.clone()
}
#[must_use]
pub fn histogram(&self, name: &'static str) -> std::sync::Arc<Histogram> {
{
let histograms = self.histograms.read();
if let Some(histogram) = histograms.get(name) {
return histogram.clone();
}
}
let mut histograms = self.histograms.write();
histograms
.entry(name)
.or_insert_with(|| std::sync::Arc::new(Histogram::new()))
.clone()
}
#[must_use]
pub fn snapshot(&self) -> MetricsSnapshot {
let counters = self.counters.read();
let histograms = self.histograms.read();
MetricsSnapshot {
counters: counters.iter().map(|(k, v)| (*k, v.get())).collect(),
histograms: histograms
.iter()
.map(|(k, v)| (*k, (v.count(), v.mean())))
.collect(),
}
}
}
impl Default for MetricsRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct MetricsSnapshot {
pub counters: HashMap<&'static str, u64>,
pub histograms: HashMap<&'static str, (u64, f64)>,
}
static METRICS: OnceLock<MetricsRegistry> = OnceLock::new();
#[must_use]
pub fn metrics() -> &'static MetricsRegistry {
METRICS.get_or_init(MetricsRegistry::new)
}