lowlet 0.1.2

Low-latency IPC library using shared memory and lock-free structures
Documentation
use std::sync::atomic::{AtomicU64, Ordering};

const CACHE_LINE: usize = 64;

#[repr(C, align(64))]
pub struct LatencyHistogram<const BUCKETS: usize> {
    counts: [AtomicU64; BUCKETS],
    min: AtomicU64,
    max: AtomicU64,
    sum: AtomicU64,
    total: AtomicU64,
    _pad: [u8; CACHE_LINE - 32],
}

impl<const BUCKETS: usize> LatencyHistogram<BUCKETS> {
    pub fn new() -> Self {
        Self {
            counts: std::array::from_fn(|_| AtomicU64::new(0)),
            min: AtomicU64::new(u64::MAX),
            max: AtomicU64::new(0),
            sum: AtomicU64::new(0),
            total: AtomicU64::new(0),
            _pad: [0; CACHE_LINE - 32],
        }
    }

    fn bucket_index(value: u64) -> usize {
        if value == 0 {
            0
        } else {
            let leading = value.leading_zeros();
            let bucket = (64 - leading) as usize;
            bucket.min(BUCKETS - 1)
        }
    }

    #[inline]
    pub fn record(&self, value: u64) {
        let idx = Self::bucket_index(value);
        if idx < BUCKETS {
            self.counts[idx].fetch_add(1, Ordering::Relaxed);
        }

        self.sum.fetch_add(value, Ordering::Relaxed);
        self.total.fetch_add(1, Ordering::Relaxed);

        let mut current_min = self.min.load(Ordering::Relaxed);
        while value < current_min {
            match self.min.compare_exchange_weak(
                current_min,
                value,
                Ordering::Relaxed,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,
                Err(v) => current_min = v,
            }
        }

        let mut current_max = self.max.load(Ordering::Relaxed);
        while value > current_max {
            match self.max.compare_exchange_weak(
                current_max,
                value,
                Ordering::Relaxed,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,
                Err(v) => current_max = v,
            }
        }
    }

    #[inline]
    pub fn record_cycles(&self, start: u64, end: u64) {
        self.record(end.saturating_sub(start));
    }

    pub fn percentile(&self, p: f64) -> u64 {
        let total = self.total.load(Ordering::Relaxed);
        if total == 0 {
            return 0;
        }

        let target = ((total as f64) * p / 100.0).ceil() as u64;
        let mut cumulative = 0u64;

        for (i, count) in self.counts.iter().enumerate() {
            cumulative += count.load(Ordering::Relaxed);
            if cumulative >= target {
                return 1u64 << i;
            }
        }

        self.max.load(Ordering::Relaxed)
    }

    #[inline]
    pub fn p50(&self) -> u64 {
        self.percentile(50.0)
    }

    #[inline]
    pub fn p90(&self) -> u64 {
        self.percentile(90.0)
    }

    #[inline]
    pub fn p99(&self) -> u64 {
        self.percentile(99.0)
    }

    #[inline]
    pub fn p999(&self) -> u64 {
        self.percentile(99.9)
    }

    pub fn mean(&self) -> f64 {
        let total = self.total.load(Ordering::Relaxed);
        if total == 0 {
            return 0.0;
        }
        self.sum.load(Ordering::Relaxed) as f64 / total as f64
    }

    #[inline]
    pub fn min(&self) -> u64 {
        let min = self.min.load(Ordering::Relaxed);
        if min == u64::MAX {
            0
        } else {
            min
        }
    }

    #[inline]
    pub fn max(&self) -> u64 {
        self.max.load(Ordering::Relaxed)
    }

    #[inline]
    pub fn count(&self) -> u64 {
        self.total.load(Ordering::Relaxed)
    }

    #[inline]
    pub fn sum(&self) -> u64 {
        self.sum.load(Ordering::Relaxed)
    }

    pub fn reset(&self) {
        for count in &self.counts {
            count.store(0, Ordering::Relaxed);
        }
        self.min.store(u64::MAX, Ordering::Relaxed);
        self.max.store(0, Ordering::Relaxed);
        self.sum.store(0, Ordering::Relaxed);
        self.total.store(0, Ordering::Relaxed);
    }

    pub fn snapshot(&self) -> HistogramSnapshot {
        HistogramSnapshot {
            min: self.min(),
            max: self.max(),
            mean: self.mean(),
            p50: self.p50(),
            p90: self.p90(),
            p99: self.p99(),
            p999: self.p999(),
            count: self.count(),
            sum: self.sum(),
        }
    }
}

impl<const BUCKETS: usize> Default for LatencyHistogram<BUCKETS> {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, Copy)]
pub struct HistogramSnapshot {
    pub min: u64,
    pub max: u64,
    pub mean: f64,
    pub p50: u64,
    pub p90: u64,
    pub p99: u64,
    pub p999: u64,
    pub count: u64,
    pub sum: u64,
}