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 ratatui::text::Span;

pub(super) const AXIS_SEGMENTS: u64 = 4;
pub(super) const Y_AXIS_LABEL_EXTRA_WIDTH: usize = 2;
pub(super) const SUCCESS_RATE_SCALE: u128 = 10_000;
pub(super) const PERCENT_DIVISOR: u64 = 100;
pub(super) const MS_PER_SEC: u64 = 1_000;
pub(super) const TENTHS_DIVISOR: u64 = 100;

pub(super) fn format_ms_as_tenths(ms: u128) -> String {
    let sec_divisor = u128::from(MS_PER_SEC);
    let tenth_divisor = u128::from(TENTHS_DIVISOR);
    let secs = ms.checked_div(sec_divisor).unwrap_or(0);
    let rem = ms.checked_rem(sec_divisor).unwrap_or(0);
    let tenths = rem.checked_div(tenth_divisor).unwrap_or(0);
    format!("{}.{}s", secs, tenths)
}

pub(super) fn format_bytes_compact(bytes: u128) -> String {
    const KB: u128 = 1_000;
    const MB: u128 = 1_000_000;
    const GB: u128 = 1_000_000_000;
    const TB: u128 = 1_000_000_000_000;

    if bytes >= TB {
        let whole = bytes / TB;
        let frac = bytes
            .saturating_sub(whole.saturating_mul(TB))
            .saturating_mul(100)
            .checked_div(TB)
            .unwrap_or(0);
        format!("{whole}.{frac:02}TB")
    } else if bytes >= GB {
        let whole = bytes / GB;
        let frac = bytes
            .saturating_sub(whole.saturating_mul(GB))
            .saturating_mul(100)
            .checked_div(GB)
            .unwrap_or(0);
        format!("{whole}.{frac:02}GB")
    } else if bytes >= MB {
        let whole = bytes / MB;
        let frac = bytes
            .saturating_sub(whole.saturating_mul(MB))
            .saturating_mul(100)
            .checked_div(MB)
            .unwrap_or(0);
        format!("{whole}.{frac:02}MB")
    } else if bytes >= KB {
        let whole = bytes / KB;
        let frac = bytes
            .saturating_sub(whole.saturating_mul(KB))
            .saturating_mul(100)
            .checked_div(KB)
            .unwrap_or(0);
        format!("{whole}.{frac:02}KB")
    } else {
        format!("{bytes}B")
    }
}

pub(super) fn format_count_compact(value: u64) -> String {
    let (scale, suffix) = select_count_scale(value);
    if suffix.is_empty() {
        return value.to_string();
    }
    format_scaled_compact(value, scale, suffix)
}

pub(super) fn format_status_bar_value(value: u64) -> String {
    if value >= 10_000 {
        let whole = value.checked_div(1_000).unwrap_or(0);
        let rem = value.checked_rem(1_000).unwrap_or(0);
        let frac = rem.saturating_mul(10).checked_div(1_000).unwrap_or(0);
        format!("{whole},{frac}k")
    } else {
        value.to_string()
    }
}

pub(super) const fn select_bytes_scale(value: u64) -> (u64, &'static str) {
    if value >= 1_000_000_000_000 {
        (1_000_000_000_000, "TB")
    } else if value >= 1_000_000_000 {
        (1_000_000_000, "GB")
    } else if value >= 1_000_000 {
        (1_000_000, "MB")
    } else if value >= 1_000 {
        (1_000, "KB")
    } else {
        (1, "B")
    }
}

pub(super) const fn select_count_scale(value: u64) -> (u64, &'static str) {
    if value >= 1_000_000_000_000 {
        (1_000_000_000_000, "t")
    } else if value >= 1_000_000_000 {
        (1_000_000_000, "g")
    } else if value >= 1_000_000 {
        (1_000_000, "m")
    } else if value >= 10_000 {
        (1_000, "k")
    } else {
        (1, "")
    }
}

fn format_scaled_compact(value: u64, scale: u64, suffix: &str) -> String {
    let whole = value.checked_div(scale).unwrap_or(0);
    let rem = value.checked_rem(scale).unwrap_or(0);

    if whole < 10 {
        let frac = rem.saturating_mul(100).checked_div(scale).unwrap_or(0);
        format!("{whole}.{frac:02}{suffix}")
    } else if whole < 100 {
        let frac = rem.saturating_mul(10).checked_div(scale).unwrap_or(0);
        format!("{whole}.{frac:01}{suffix}")
    } else {
        format!("{whole}{suffix}")
    }
}

pub(super) fn axis_tick_value(max_value: u64, step: u64) -> u64 {
    max_value
        .saturating_mul(step)
        .checked_div(AXIS_SEGMENTS)
        .unwrap_or(0)
}

pub(super) fn centered_axis_labels(labels: [String; 5]) -> Vec<Span<'static>> {
    centered_axis_labels_with_extra(labels, 0)
}

pub(super) fn centered_y_axis_labels(labels: [String; 5]) -> Vec<Span<'static>> {
    centered_axis_labels_with_extra(labels, Y_AXIS_LABEL_EXTRA_WIDTH)
}

fn centered_axis_labels_with_extra(labels: [String; 5], extra: usize) -> Vec<Span<'static>> {
    let width = labels.iter().map(|label| label.len()).max().unwrap_or(1);
    let width = width.saturating_add(extra);
    labels
        .into_iter()
        .map(|label| Span::raw(format!("{label:^w$}", w = width)))
        .collect()
}