latency-buckets 0.1.0

Streaming histogram + percentile estimator for LLM call latencies. Fixed log-scale buckets, O(1) record, p50/p90/p95/p99 in microseconds. Zero deps.
Documentation
use latency_buckets::Histogram;
use std::time::Duration;

#[test]
fn empty_is_zero() {
    let h = Histogram::new();
    assert_eq!(h.count(), 0);
    assert_eq!(h.percentile(0.5), Duration::ZERO);
}

#[test]
fn single_sample_returns_within_its_bucket() {
    let mut h = Histogram::new();
    h.record(Duration::from_millis(100));
    let p = h.percentile(0.5);
    // Bucket spans some range; midpoint should be within 2x of input.
    assert!(p.as_millis() >= 50 && p.as_millis() <= 200, "got {p:?}");
}

#[test]
fn p50_increases_with_higher_workload() {
    let mut h = Histogram::new();
    for ms in [10, 10, 10, 1000, 1000, 1000] {
        h.record(Duration::from_millis(ms));
    }
    let p50 = h.percentile(0.5);
    // Should be in the middle ~ between 10ms and 1s; bucket midpoints
    // are coarse but the answer should be > 10ms.
    assert!(p50.as_millis() > 10);
}

#[test]
fn p95_lands_in_tail() {
    let mut h = Histogram::new();
    for _ in 0..90 {
        h.record(Duration::from_millis(10));
    }
    for _ in 0..10 {
        h.record(Duration::from_millis(5000));
    }
    let p95 = h.percentile(0.95);
    assert!(p95.as_millis() > 100, "p95 should reflect the tail; got {p95:?}");
}

#[test]
fn count_tracks_records() {
    let mut h = Histogram::new();
    for _ in 0..10 {
        h.record(Duration::from_millis(1));
    }
    assert_eq!(h.count(), 10);
}