//! Load test configuration and scenario distribution. use std::time::Duration; /// Top-level configuration for a load test run. pub struct LoadConfig { /// Number of concurrent virtual users. pub virtual_users: u32, /// Total duration of the test. pub duration: Duration, /// Time to linearly ramp up all VUs. pub ramp_up: Duration, /// Pause between requests within a scenario loop. pub think_time: Duration, /// Max DB connections for the production-sized pool. pub db_max_connections: u32, /// DB connection acquire timeout. pub db_acquire_timeout: Duration, /// Scenario distribution across VUs. pub scenario_mix: ScenarioMix, } impl LoadConfig { /// Build config from env vars, falling back to defaults. pub fn from_env() -> Self { let virtual_users = env_or("LOAD_VUS", 20); let duration = Duration::from_secs(env_or("LOAD_DURATION_SECS", 30)); let ramp_up = Duration::from_secs(env_or("LOAD_RAMP_SECS", 5)); let think_time = Duration::from_millis(env_or("LOAD_THINK_MS", 50)); LoadConfig { virtual_users, duration, ramp_up, think_time, db_max_connections: 10, db_acquire_timeout: Duration::from_secs(3), scenario_mix: ScenarioMix::default(), } } } /// Percentage-based scenario distribution. Must sum to 100. #[derive(Debug)] pub struct ScenarioMix { pub anonymous_browse: u32, pub buyer_flow: u32, pub creator_flow: u32, pub dashboard_session: u32, } impl Default for ScenarioMix { fn default() -> Self { ScenarioMix { anonymous_browse: 60, buyer_flow: 20, creator_flow: 15, dashboard_session: 5, } } } impl ScenarioMix { /// Deterministically assign a scenario to a VU based on its index. pub fn assign_scenario(&self, vu_index: u32, total_vus: u32) -> ScenarioType { // Map the VU index to a percentage position (0..100) let pct = (vu_index as u64 * 100 / total_vus as u64) as u32; if pct < self.anonymous_browse { ScenarioType::AnonymousBrowse } else if pct < self.anonymous_browse + self.buyer_flow { ScenarioType::BuyerFlow } else if pct < self.anonymous_browse + self.buyer_flow + self.creator_flow { ScenarioType::CreatorFlow } else { ScenarioType::DashboardSession } } } #[derive(Debug, Clone, Copy)] pub enum ScenarioType { AnonymousBrowse, BuyerFlow, CreatorFlow, DashboardSession, } impl std::fmt::Display for ScenarioType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ScenarioType::AnonymousBrowse => write!(f, "anonymous_browse"), ScenarioType::BuyerFlow => write!(f, "buyer_flow"), ScenarioType::CreatorFlow => write!(f, "creator_flow"), ScenarioType::DashboardSession => write!(f, "dashboard_session"), } } } fn env_or(key: &str, default: T) -> T { std::env::var(key) .ok() .and_then(|v| v.parse().ok()) .unwrap_or(default) }