strest 0.1.10

Blazing-fast async HTTP load tester in Rust - lock-free design, real-time stats, distributed runs, and optional chart exports for high-load API testing.
Documentation
use std::collections::VecDeque;
use std::time::Duration;

use tokio::time::Instant;

pub(in crate::metrics::collector) fn prune_latency_window(
    window: &mut VecDeque<(Instant, u64)>,
    now: Instant,
    window_span: Duration,
) {
    while window
        .front()
        .is_some_and(|(ts, _)| now.duration_since(*ts) > window_span)
    {
        window.pop_front();
    }
}

pub(in crate::metrics::collector) fn prune_rps_window(
    window: &mut VecDeque<(Instant, u64)>,
    now: Instant,
) {
    while window
        .front()
        .is_some_and(|(ts, _)| now.duration_since(*ts) > Duration::from_secs(60))
    {
        window.pop_front();
    }
}

pub(in crate::metrics::collector) fn prune_bytes_window(
    window: &mut VecDeque<(Instant, u64)>,
    now: Instant,
) {
    while window
        .front()
        .is_some_and(|(ts, _)| now.duration_since(*ts) > Duration::from_secs(60))
    {
        window.pop_front();
    }
}

pub(in crate::metrics::collector) fn record_rps_sample(
    samples: &mut VecDeque<(Instant, u64)>,
    now: Instant,
    rps: u64,
    window_span: Duration,
) {
    samples.push_back((now, rps));
    prune_rps_samples(samples, now, window_span);
}

fn prune_rps_samples(samples: &mut VecDeque<(Instant, u64)>, now: Instant, window_span: Duration) {
    while samples
        .front()
        .is_some_and(|(ts, _)| now.duration_since(*ts) > window_span)
    {
        samples.pop_front();
    }
}

pub(in crate::metrics::collector) fn record_bytes_sample(
    samples: &mut VecDeque<(Instant, u64)>,
    now: Instant,
    bytes_per_sec: u64,
    window_span: Duration,
) {
    samples.push_back((now, bytes_per_sec));
    prune_bytes_samples(samples, now, window_span);
}

fn prune_bytes_samples(
    samples: &mut VecDeque<(Instant, u64)>,
    now: Instant,
    window_span: Duration,
) {
    while samples
        .front()
        .is_some_and(|(ts, _)| now.duration_since(*ts) > window_span)
    {
        samples.pop_front();
    }
}

pub(in crate::metrics::collector) fn compute_percentiles(
    window: &VecDeque<(Instant, u64)>,
) -> (u64, u64, u64) {
    if window.is_empty() {
        return (0, 0, 0);
    }

    let mut values: Vec<u64> = window.iter().map(|(_, latency)| *latency).collect();
    values.sort_unstable();

    let p50 = percentile(&values, 50);
    let p90 = percentile(&values, 90);
    let p99 = percentile(&values, 99);

    (p50, p90, p99)
}

fn percentile(data: &[u64], percentile: u64) -> u64 {
    if data.is_empty() {
        return 0;
    }
    let count = data.len().saturating_sub(1) as u64;
    let index = (percentile.saturating_mul(count).saturating_add(50) / 100) as usize;
    *data.get(index).unwrap_or(&0)
}