use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use parking_lot::Mutex;
use super::value_objects::MetricName;
#[derive(Debug, Clone)]
pub struct Counter {
pub name: MetricName,
value: Arc<AtomicU64>,
}
impl Counter {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: MetricName::new(name),
value: Arc::new(AtomicU64::new(0)),
}
}
pub fn with_value(name: impl Into<String>, initial: u64) -> Self {
Self {
name: MetricName::new(name),
value: Arc::new(AtomicU64::new(initial)),
}
}
pub fn inc(&self, delta: u64) {
self.value.fetch_add(delta, Ordering::Relaxed);
}
pub fn get(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn name(&self) -> &MetricName {
&self.name
}
}
impl PartialEq for Counter {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
#[derive(Debug, Clone)]
pub struct Gauge {
pub name: MetricName,
value: Arc<Mutex<f64>>,
}
impl Gauge {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: MetricName::new(name),
value: Arc::new(Mutex::new(0.0)),
}
}
pub fn with_value(name: impl Into<String>, initial: f64) -> Self {
Self {
name: MetricName::new(name),
value: Arc::new(Mutex::new(initial)),
}
}
pub fn set(&self, val: f64) {
*self.value.lock() = val;
}
pub fn add(&self, delta: f64) {
let mut v = self.value.lock();
*v += delta;
}
pub fn sub(&self, delta: f64) {
let mut v = self.value.lock();
*v -= delta;
}
pub fn get(&self) -> f64 {
*self.value.lock()
}
pub fn name(&self) -> &MetricName {
&self.name
}
}
#[derive(Debug, Clone)]
pub struct Histogram {
pub name: MetricName,
data: Arc<HistogramData>,
}
#[derive(Debug, Clone)]
struct HistogramData {
values: Arc<parking_lot::Mutex<Vec<u64>>>,
count: Arc<AtomicU64>,
sum: Arc<AtomicU64>,
min: Arc<AtomicU64>,
max: Arc<AtomicU64>,
}
impl Histogram {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: MetricName::new(name),
data: Arc::new(HistogramData {
values: Arc::new(parking_lot::Mutex::new(Vec::with_capacity(1000))),
count: Arc::new(AtomicU64::new(0)),
sum: Arc::new(AtomicU64::new(0)),
min: Arc::new(AtomicU64::new(u64::MAX)),
max: Arc::new(AtomicU64::new(0)),
}),
}
}
pub fn name(&self) -> &MetricName {
&self.name
}
pub fn record(&self, value: u64) {
let mut values = self.data.values.lock();
values.push(value);
values.sort_unstable();
self.data.count.fetch_add(1, Ordering::Relaxed);
self.data.sum.fetch_add(value, Ordering::Relaxed);
loop {
let current_min = self.data.min.load(Ordering::Relaxed);
if value >= current_min || self.data.min.compare_exchange(current_min, value, Ordering::Relaxed, Ordering::Relaxed).is_ok() {
break;
}
}
loop {
let current_max = self.data.max.load(Ordering::Relaxed);
if value <= current_max || self.data.max.compare_exchange(current_max, value, Ordering::Relaxed, Ordering::Relaxed).is_ok() {
break;
}
}
}
pub fn count(&self) -> u64 {
self.data.count.load(Ordering::Relaxed)
}
pub fn sum(&self) -> u64 {
self.data.sum.load(Ordering::Relaxed)
}
pub fn min(&self) -> u64 {
let min = self.data.min.load(Ordering::Relaxed);
if min == u64::MAX { 0 } else { min }
}
pub fn max(&self) -> u64 {
self.data.max.load(Ordering::Relaxed)
}
pub fn mean(&self) -> f64 {
let count = self.count();
if count == 0 { 0.0 } else { self.sum() as f64 / count as f64 }
}
pub fn percentile(&self, p: f64) -> f64 {
let values = self.data.values.lock();
if values.is_empty() { return 0.0; }
let idx = ((p / 100.0) * values.len() as f64) as usize;
let idx = idx.min(values.len() - 1);
values[idx] as f64
}
pub fn p50(&self) -> f64 {
self.percentile(50.0)
}
pub fn p90(&self) -> f64 {
self.percentile(90.0)
}
pub fn p99(&self) -> f64 {
self.percentile(99.0)
}
pub fn p999(&self) -> f64 {
self.percentile(99.9)
}
}
#[derive(Debug, Clone)]
pub struct Summary {
pub name: MetricName,
count: Arc<AtomicU64>,
sum: Arc<parking_lot::Mutex<f64>>,
}
impl Summary {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: MetricName::new(name),
count: Arc::new(AtomicU64::new(0)),
sum: Arc::new(parking_lot::Mutex::new(0.0)),
}
}
pub fn name(&self) -> &MetricName {
&self.name
}
pub fn observe(&self, value: f64) {
self.count.fetch_add(1, Ordering::Relaxed);
*self.sum.lock() += value;
}
pub fn count(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
pub fn sum(&self) -> f64 {
*self.sum.lock()
}
pub fn mean(&self) -> f64 {
let c = self.count();
if c == 0 { 0.0 } else { self.sum() / c as f64 }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter_basic() {
let counter = Counter::new("requests");
assert_eq!(counter.get(), 0);
counter.inc(1);
assert_eq!(counter.get(), 1);
counter.inc(5);
assert_eq!(counter.get(), 6);
}
#[test]
fn test_counter_clone_shares_state() {
let counter = Counter::new("test");
let clone = counter.clone();
counter.inc(10);
assert_eq!(clone.get(), 10);
}
#[test]
fn test_gauge_basic() {
let gauge = Gauge::new("memory");
assert_eq!(gauge.get(), 0.0);
gauge.set(1024.0);
assert_eq!(gauge.get(), 1024.0);
gauge.add(512.0);
assert_eq!(gauge.get(), 1536.0);
gauge.sub(256.0);
assert_eq!(gauge.get(), 1280.0);
}
#[test]
fn test_histogram_basic() {
let hist = Histogram::new("latency");
hist.record(5);
hist.record(15);
hist.record(25);
assert_eq!(hist.count(), 3);
assert!((hist.p50() - 15.0).abs() < f64::EPSILON);
assert_eq!(hist.min(), 5);
assert_eq!(hist.max(), 25);
}
#[test]
fn test_histogram_empty() {
let hist = Histogram::new("empty");
assert_eq!(hist.count(), 0);
assert_eq!(hist.p50(), 0.0);
assert_eq!(hist.mean(), 0.0);
}
#[test]
fn test_histogram_percentiles() {
let hist = Histogram::new("latency");
for i in 1..=100 {
hist.record(i);
}
let p50 = hist.p50();
let p90 = hist.p90();
let p99 = hist.p99();
assert!(p50 >= 50.0 && p50 <= 51.0);
assert!(p90 >= 90.0 && p90 <= 91.0);
assert!(p99 >= 99.0 && p99 <= 100.0);
}
#[test]
fn test_summary_basic() {
let summary = Summary::new("request_duration");
summary.observe(10.0);
summary.observe(20.0);
summary.observe(30.0);
assert_eq!(summary.count(), 3);
assert!((summary.sum() - 60.0).abs() < f64::EPSILON);
assert!((summary.mean() - 20.0).abs() < f64::EPSILON);
}
}