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;

use crate::ui::model::UiRenderData;

use super::formatting::{
    MS_PER_SEC, TENTHS_DIVISOR, axis_tick_value, centered_axis_labels, centered_y_axis_labels,
    format_count_compact, select_bytes_scale,
};
use super::theme::{MIN_DATA_Y_MAX, MIN_LATENCY_MS, MIN_RPS_Y_MAX, MIN_WINDOW_MS, MIN_Y_MAX};

pub struct ChartWindowData {
    pub window_label: String,
    pub x_span: u64,
    pub y_max: u64,
    pub rps_y_max: u64,
    pub data_y_max_scaled: u64,
    pub data_unit: &'static str,
    pub x_axis_labels: Vec<Span<'static>>,
    pub latency_y_labels: Vec<Span<'static>>,
    pub rps_y_labels: Vec<Span<'static>>,
    pub data_y_labels: Vec<Span<'static>>,
    pub latency_chart_points: Vec<(f64, f64)>,
    pub compare_latency_chart_points: Vec<(f64, f64)>,
    pub rps_chart_points: Vec<(f64, f64)>,
    pub compare_rps_chart_points: Vec<(f64, f64)>,
    pub data_chart_points: Vec<(f64, f64)>,
    pub compare_data_chart_points: Vec<(f64, f64)>,
}

pub fn compute_chart_window(data: &UiRenderData) -> ChartWindowData {
    let compare = data.compare.as_ref();
    let latency_points = data.latencies.as_slice();
    let rps_points = data.rps_series.as_slice();
    let data_points = data
        .data_usage
        .as_ref()
        .map(|usage| usage.series.as_slice())
        .unwrap_or(&[]);
    let compare_latency_points = compare
        .map(|value| value.latencies.as_slice())
        .unwrap_or(&[]);
    let compare_rps_points = compare
        .map(|value| value.rps_series.as_slice())
        .unwrap_or(&[]);
    let compare_data_points = compare
        .and_then(|value| value.data_usage.as_ref())
        .map(|usage| usage.series.as_slice())
        .unwrap_or(&[]);

    let mut y_max = data
        .latencies
        .iter()
        .map(|(_, latency)| (*latency).max(MIN_LATENCY_MS))
        .fold(MIN_LATENCY_MS, u64::max)
        .max(MIN_Y_MAX);
    if let Some(compare) = compare {
        let compare_max = compare
            .latencies
            .iter()
            .map(|(_, latency)| (*latency).max(MIN_LATENCY_MS))
            .fold(MIN_LATENCY_MS, u64::max);
        y_max = y_max.max(compare_max);
    }

    let mut rps_y_max = data
        .rps_series
        .iter()
        .map(|(_, value)| *value)
        .fold(0, u64::max)
        .max(MIN_RPS_Y_MAX);
    if let Some(compare) = compare {
        let compare_max = compare
            .rps_series
            .iter()
            .map(|(_, value)| *value)
            .fold(0, u64::max);
        rps_y_max = rps_y_max.max(compare_max);
    }

    let mut data_y_max = data_points
        .iter()
        .map(|(_, value)| *value)
        .fold(0, u64::max)
        .max(MIN_DATA_Y_MAX);
    let compare_data_y_max = compare_data_points
        .iter()
        .map(|(_, value)| *value)
        .fold(0, u64::max);
    data_y_max = data_y_max.max(compare_data_y_max);

    let window_ms = data.ui_window_ms.max(MIN_WINDOW_MS);
    let x_max = latency_points
        .last()
        .map(|(x, _)| *x)
        .unwrap_or(0)
        .max(rps_points.last().map(|(x, _)| *x).unwrap_or(0))
        .max(data_points.last().map(|(x, _)| *x).unwrap_or(0))
        .max(compare_latency_points.last().map(|(x, _)| *x).unwrap_or(0))
        .max(compare_rps_points.last().map(|(x, _)| *x).unwrap_or(0))
        .max(compare_data_points.last().map(|(x, _)| *x).unwrap_or(0));
    let x_start = x_max.saturating_sub(window_ms);
    let x_span = x_max.saturating_sub(x_start).max(MIN_WINDOW_MS);

    let latency_chart_points: Vec<(f64, f64)> = latency_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| {
            (
                x.saturating_sub(x_start) as f64,
                (*y).max(MIN_LATENCY_MS) as f64,
            )
        })
        .collect();
    let compare_latency_chart_points: Vec<(f64, f64)> = compare_latency_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| {
            (
                x.saturating_sub(x_start) as f64,
                (*y).max(MIN_LATENCY_MS) as f64,
            )
        })
        .collect();
    let rps_chart_points: Vec<(f64, f64)> = rps_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| (x.saturating_sub(x_start) as f64, *y as f64))
        .collect();
    let compare_rps_chart_points: Vec<(f64, f64)> = compare_rps_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| (x.saturating_sub(x_start) as f64, *y as f64))
        .collect();

    let (data_scale, data_unit) = select_bytes_scale(data_y_max);
    let data_y_max_scaled = data_y_max.checked_div(data_scale).unwrap_or(0).max(1);
    let data_chart_points: Vec<(f64, f64)> = data_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| {
            let scaled = y.checked_div(data_scale).unwrap_or(0);
            (x.saturating_sub(x_start) as f64, scaled as f64)
        })
        .collect();
    let compare_data_chart_points: Vec<(f64, f64)> = compare_data_points
        .iter()
        .filter(|(x, _)| *x >= x_start)
        .map(|(x, y)| {
            let scaled = y.checked_div(data_scale).unwrap_or(0);
            (x.saturating_sub(x_start) as f64, scaled as f64)
        })
        .collect();

    let fmt_secs = |ms: u64| {
        let secs = ms / MS_PER_SEC;
        let tenths = (ms % MS_PER_SEC) / TENTHS_DIVISOR;
        format!("{}.{:01}s", secs, tenths)
    };
    let label_left = x_start;
    let label_low_mid = x_start.saturating_add(axis_tick_value(x_span, 1));
    let label_mid = x_start.saturating_add(x_span / 2);
    let label_mid_high = x_start.saturating_add(axis_tick_value(x_span, 3));
    let label_right = x_start.saturating_add(x_span);
    let x_axis_labels = centered_axis_labels([
        fmt_secs(label_left),
        fmt_secs(label_low_mid),
        fmt_secs(label_mid),
        fmt_secs(label_mid_high),
        fmt_secs(label_right),
    ]);
    let latency_y_labels = centered_y_axis_labels([
        format_count_compact(axis_tick_value(y_max, 0)),
        format_count_compact(axis_tick_value(y_max, 1)),
        format_count_compact(axis_tick_value(y_max, 2)),
        format_count_compact(axis_tick_value(y_max, 3)),
        format_count_compact(y_max),
    ]);
    let rps_y_labels = centered_y_axis_labels([
        format_count_compact(axis_tick_value(rps_y_max, 0)),
        format_count_compact(axis_tick_value(rps_y_max, 1)),
        format_count_compact(axis_tick_value(rps_y_max, 2)),
        format_count_compact(axis_tick_value(rps_y_max, 3)),
        format_count_compact(rps_y_max),
    ]);
    let data_y_labels = centered_axis_labels([
        format_count_compact(axis_tick_value(data_y_max_scaled, 0)),
        format_count_compact(axis_tick_value(data_y_max_scaled, 1)),
        format_count_compact(axis_tick_value(data_y_max_scaled, 2)),
        format_count_compact(axis_tick_value(data_y_max_scaled, 3)),
        format_count_compact(data_y_max_scaled),
    ]);

    ChartWindowData {
        window_label: fmt_secs(window_ms),
        x_span,
        y_max,
        rps_y_max,
        data_y_max_scaled,
        data_unit,
        x_axis_labels,
        latency_y_labels,
        rps_y_labels,
        data_y_labels,
        latency_chart_points,
        compare_latency_chart_points,
        rps_chart_points,
        compare_rps_chart_points,
        data_chart_points,
        compare_data_chart_points,
    }
}