Skip to main content

max / makenotwork

3.2 KB · 106 lines History Blame Raw
1 //! Load test configuration and scenario distribution.
2
3 use std::time::Duration;
4
5 /// Top-level configuration for a load test run.
6 pub struct LoadConfig {
7 /// Number of concurrent virtual users.
8 pub virtual_users: u32,
9 /// Total duration of the test.
10 pub duration: Duration,
11 /// Time to linearly ramp up all VUs.
12 pub ramp_up: Duration,
13 /// Pause between requests within a scenario loop.
14 pub think_time: Duration,
15 /// Max DB connections for the production-sized pool.
16 pub db_max_connections: u32,
17 /// DB connection acquire timeout.
18 pub db_acquire_timeout: Duration,
19 /// Scenario distribution across VUs.
20 pub scenario_mix: ScenarioMix,
21 }
22
23 impl LoadConfig {
24 /// Build config from env vars, falling back to defaults.
25 pub fn from_env() -> Self {
26 let virtual_users = env_or("LOAD_VUS", 20);
27 let duration = Duration::from_secs(env_or("LOAD_DURATION_SECS", 30));
28 let ramp_up = Duration::from_secs(env_or("LOAD_RAMP_SECS", 5));
29 let think_time = Duration::from_millis(env_or("LOAD_THINK_MS", 50));
30
31 LoadConfig {
32 virtual_users,
33 duration,
34 ramp_up,
35 think_time,
36 db_max_connections: 10,
37 db_acquire_timeout: Duration::from_secs(3),
38 scenario_mix: ScenarioMix::default(),
39 }
40 }
41 }
42
43 /// Percentage-based scenario distribution. Must sum to 100.
44 #[derive(Debug)]
45 pub struct ScenarioMix {
46 pub anonymous_browse: u32,
47 pub buyer_flow: u32,
48 pub creator_flow: u32,
49 pub dashboard_session: u32,
50 }
51
52 impl Default for ScenarioMix {
53 fn default() -> Self {
54 ScenarioMix {
55 anonymous_browse: 60,
56 buyer_flow: 20,
57 creator_flow: 15,
58 dashboard_session: 5,
59 }
60 }
61 }
62
63 impl ScenarioMix {
64 /// Deterministically assign a scenario to a VU based on its index.
65 pub fn assign_scenario(&self, vu_index: u32, total_vus: u32) -> ScenarioType {
66 // Map the VU index to a percentage position (0..100)
67 let pct = (vu_index as u64 * 100 / total_vus as u64) as u32;
68
69 if pct < self.anonymous_browse {
70 ScenarioType::AnonymousBrowse
71 } else if pct < self.anonymous_browse + self.buyer_flow {
72 ScenarioType::BuyerFlow
73 } else if pct < self.anonymous_browse + self.buyer_flow + self.creator_flow {
74 ScenarioType::CreatorFlow
75 } else {
76 ScenarioType::DashboardSession
77 }
78 }
79 }
80
81 #[derive(Debug, Clone, Copy)]
82 pub enum ScenarioType {
83 AnonymousBrowse,
84 BuyerFlow,
85 CreatorFlow,
86 DashboardSession,
87 }
88
89 impl std::fmt::Display for ScenarioType {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 ScenarioType::AnonymousBrowse => write!(f, "anonymous_browse"),
93 ScenarioType::BuyerFlow => write!(f, "buyer_flow"),
94 ScenarioType::CreatorFlow => write!(f, "creator_flow"),
95 ScenarioType::DashboardSession => write!(f, "dashboard_session"),
96 }
97 }
98 }
99
100 fn env_or<T: std::str::FromStr>(key: &str, default: T) -> T {
101 std::env::var(key)
102 .ok()
103 .and_then(|v| v.parse().ok())
104 .unwrap_or(default)
105 }
106