mod effect;
mod fiber;
mod registry;
pub use effect::*;
pub use fiber::*;
pub use registry::*;
use alloc::string::String;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct Numerator {
value: AtomicU64,
}
impl Numerator {
#[inline]
pub fn new() -> Self {
Numerator {
value: AtomicU64::new(0),
}
}
#[inline]
pub fn increment(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn add(&self, value: u64) {
self.value.fetch_add(value, Ordering::Relaxed);
}
#[inline]
pub fn value(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
}
impl Default for Numerator {
fn default() -> Self {
Self::new()
}
}
impl Clone for Numerator {
fn clone(&self) -> Self {
Numerator {
value: AtomicU64::new(self.value()),
}
}
}
#[derive(Debug)]
pub struct Indicium {
value: AtomicU64,
}
impl Indicium {
#[inline]
pub fn new() -> Self {
Indicium {
value: AtomicU64::new(0),
}
}
#[inline]
pub fn set(&self, value: u64) {
self.value.store(value, Ordering::Relaxed);
}
#[inline]
pub fn increment(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn decrement(&self) {
let _ = self
.value
.try_update(Ordering::Relaxed, Ordering::Relaxed, |v| v.checked_sub(1));
}
#[inline]
pub fn value(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
}
impl Default for Indicium {
fn default() -> Self {
Self::new()
}
}
impl Clone for Indicium {
fn clone(&self) -> Self {
Indicium {
value: AtomicU64::new(self.value()),
}
}
}
#[derive(Debug)]
pub struct Distributio {
boundaries: &'static [u64],
buckets: Vec<AtomicU64>,
sum: AtomicU64,
count: AtomicU64,
}
impl Distributio {
pub const LATENCY_BUCKETS: &'static [u64] = &[
1_000, 5_000, 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_500_000, 5_000_000, 10_000_000, 25_000_000, 50_000_000, 100_000_000, 250_000_000, 500_000_000, 1_000_000_000, ];
pub fn new() -> Self {
Self::with_boundaries(Self::LATENCY_BUCKETS)
}
pub fn with_boundaries(boundaries: &'static [u64]) -> Self {
let mut buckets = Vec::with_capacity(boundaries.len() + 1);
for _ in 0..=boundaries.len() {
buckets.push(AtomicU64::new(0));
}
Distributio {
boundaries,
buckets,
sum: AtomicU64::new(0),
count: AtomicU64::new(0),
}
}
#[inline]
pub fn record(&self, value: u64) {
let bucket_idx = self
.boundaries
.iter()
.position(|&b| value <= b)
.unwrap_or(self.boundaries.len());
self.buckets[bucket_idx].fetch_add(1, Ordering::Relaxed);
self.sum.fetch_add(value, Ordering::Relaxed);
self.count.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn count(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
#[inline]
pub fn sum(&self) -> u64 {
self.sum.load(Ordering::Relaxed)
}
#[inline]
#[allow(clippy::cast_precision_loss)]
pub fn mean(&self) -> f64 {
let count = self.count();
if count == 0 {
0.0
} else {
self.sum() as f64 / count as f64
}
}
#[inline]
pub fn bucket_counts(&self) -> Vec<u64> {
self.buckets
.iter()
.map(|b| b.load(Ordering::Relaxed))
.collect()
}
#[inline]
pub fn boundaries(&self) -> &[u64] {
self.boundaries
}
pub fn percentile(&self, p: f64) -> Option<u64> {
let count = self.count();
if count == 0 {
return None;
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
let target = (count as f64 * p / 100.0).max(0.0).min(u64::MAX as f64) as u64;
let mut cumulative = 0u64;
for (i, bucket) in self.buckets.iter().enumerate() {
cumulative += bucket.load(Ordering::Relaxed);
if cumulative >= target {
if i < self.boundaries.len() {
return Some(self.boundaries[i]);
}
return self.boundaries.last().copied();
}
}
self.boundaries.last().copied()
}
}
impl Default for Distributio {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MetricSnapshot {
pub name: String,
pub metric_type: MetricType,
pub value: MetricValue,
pub labels: Vec<(String, String)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetricType {
Counter,
Gauge,
Histogram,
}
#[derive(Debug, Clone)]
pub enum MetricValue {
Counter(u64),
Gauge(u64),
Histogram {
count: u64,
sum: u64,
buckets: Vec<(u64, u64)>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_numerator() {
let counter = Numerator::new();
assert_eq!(counter.value(), 0);
counter.increment();
assert_eq!(counter.value(), 1);
counter.add(5);
assert_eq!(counter.value(), 6);
}
#[test]
fn test_indicium() {
let gauge = Indicium::new();
assert_eq!(gauge.value(), 0);
gauge.set(10);
assert_eq!(gauge.value(), 10);
gauge.increment();
assert_eq!(gauge.value(), 11);
gauge.decrement();
assert_eq!(gauge.value(), 10);
}
#[test]
fn test_indicium_decrement_saturates_at_zero() {
let gauge = Indicium::new();
assert_eq!(gauge.value(), 0);
gauge.decrement();
assert_eq!(gauge.value(), 0); }
#[test]
fn test_distributio() {
let hist = Distributio::new();
hist.record(500); hist.record(5_000); hist.record(50_000);
assert_eq!(hist.count(), 3);
assert_eq!(hist.sum(), 55_500);
}
#[test]
fn test_distributio_percentile() {
let hist = Distributio::new();
for _ in 0..100 {
hist.record(3_000);
}
let p50 = hist.percentile(50.0);
assert!(p50.is_some());
}
}