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 crate::args::{LoadProfile, TesterArgs};

pub(super) fn apply_load_share(
    agent_args: &mut super::super::protocol::WireArgs,
    args: &TesterArgs,
    weights: &[u64],
    idx: usize,
) {
    let use_weights = weights.iter().any(|value| *value != 1);
    let share_weights: Vec<u64> = if use_weights {
        weights.to_vec()
    } else {
        vec![1; weights.len()]
    };

    if let Some(profile) = args.load_profile.as_ref() {
        let split = split_load_profile(profile, &share_weights);
        if let Some(agent_profile) = split.get(idx) {
            agent_args.load_profile = Some(agent_profile.clone());
            agent_args.rate_limit = None;
        }
        return;
    }

    if let Some(rate) = args.rate_limit.map(u64::from) {
        let shares = split_total(rate, &share_weights);
        if let Some(share) = shares.get(idx) {
            agent_args.rate_limit = Some(*share);
        }
    }
}

fn split_load_profile(
    profile: &LoadProfile,
    weights: &[u64],
) -> Vec<super::super::protocol::WireLoadProfile> {
    let initial_shares = split_total(profile.initial_rpm, weights);
    let mut stage_shares: Vec<Vec<u64>> = Vec::new();
    for stage in &profile.stages {
        stage_shares.push(split_total(stage.target_rpm, weights));
    }

    let mut per_agent = Vec::with_capacity(weights.len());
    for idx in 0..weights.len() {
        let mut stages = Vec::with_capacity(profile.stages.len());
        for (stage_idx, stage) in profile.stages.iter().enumerate() {
            let share = stage_shares
                .get(stage_idx)
                .and_then(|values| values.get(idx))
                .copied()
                .unwrap_or(0);
            stages.push(super::super::protocol::WireLoadStage {
                duration_secs: stage.duration.as_secs(),
                target_rpm: share,
            });
        }
        let initial_rpm = initial_shares.get(idx).copied().unwrap_or(0);
        per_agent.push(super::super::protocol::WireLoadProfile {
            initial_rpm,
            stages,
        });
    }

    per_agent
}

fn split_total(total: u64, weights: &[u64]) -> Vec<u64> {
    if weights.is_empty() {
        return Vec::new();
    }
    let total_weight: u128 = weights.iter().map(|value| u128::from(*value)).sum();
    if total_weight == 0 {
        return vec![0; weights.len()];
    }

    let mut shares = vec![0u64; weights.len()];
    let mut remainder = u128::from(total);
    for (idx, weight) in weights.iter().enumerate() {
        let share = u128::from(total)
            .saturating_mul(u128::from(*weight))
            .checked_div(total_weight)
            .unwrap_or(0);
        if let Some(slot) = shares.get_mut(idx) {
            *slot = u64::try_from(share).unwrap_or(u64::MAX);
        }
        remainder = remainder.saturating_sub(share);
    }

    let mut idx = 0usize;
    while remainder > 0 {
        if let Some(value) = shares.get_mut(idx) {
            *value = value.saturating_add(1);
        }
        remainder = remainder.saturating_sub(1);
        idx = idx.saturating_add(1);
        if idx >= shares.len() {
            idx = 0;
        }
    }

    shares
}