1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "kebab-case")]
8pub enum LoadScenario {
9 Constant,
11 RampUp,
13 Spike,
15 Stress,
17 Soak,
19}
20
21impl std::str::FromStr for LoadScenario {
22 type Err = String;
23
24 fn from_str(s: &str) -> Result<Self, Self::Err> {
25 match s.to_lowercase().as_str() {
26 "constant" => Ok(Self::Constant),
27 "ramp-up" | "ramp_up" | "rampup" => Ok(Self::RampUp),
28 "spike" => Ok(Self::Spike),
29 "stress" => Ok(Self::Stress),
30 "soak" => Ok(Self::Soak),
31 _ => Err(format!("Unknown scenario: {}", s)),
32 }
33 }
34}
35
36impl LoadScenario {
37 pub fn generate_stages(&self, duration_secs: u64, max_vus: u32) -> Vec<Stage> {
39 match self {
40 Self::Constant => {
41 vec![Stage {
42 duration: format!("{}s", duration_secs),
43 target: max_vus,
44 }]
45 }
46 Self::RampUp => {
47 let ramp_duration = duration_secs / 3;
48 let sustain_duration = duration_secs / 3;
49 let ramp_down_duration = duration_secs - ramp_duration - sustain_duration;
50
51 vec![
52 Stage {
53 duration: format!("{}s", ramp_duration / 2),
54 target: max_vus / 4,
55 },
56 Stage {
57 duration: format!("{}s", ramp_duration / 2),
58 target: max_vus / 2,
59 },
60 Stage {
61 duration: format!("{}s", sustain_duration),
62 target: max_vus,
63 },
64 Stage {
65 duration: format!("{}s", ramp_down_duration),
66 target: 0,
67 },
68 ]
69 }
70 Self::Spike => {
71 let baseline_duration = duration_secs / 5;
72 let spike_duration = duration_secs / 10;
73 let recovery_duration = duration_secs - (baseline_duration * 2) - spike_duration;
74
75 vec![
76 Stage {
77 duration: format!("{}s", baseline_duration),
78 target: max_vus / 10,
79 },
80 Stage {
81 duration: format!("{}s", spike_duration),
82 target: max_vus,
83 },
84 Stage {
85 duration: format!("{}s", recovery_duration),
86 target: max_vus / 10,
87 },
88 Stage {
89 duration: format!("{}s", baseline_duration),
90 target: 0,
91 },
92 ]
93 }
94 Self::Stress => {
95 let step_duration = duration_secs / 6;
96 let step_vus = max_vus / 5;
97
98 vec![
99 Stage {
100 duration: format!("{}s", step_duration),
101 target: step_vus,
102 },
103 Stage {
104 duration: format!("{}s", step_duration),
105 target: step_vus * 2,
106 },
107 Stage {
108 duration: format!("{}s", step_duration),
109 target: step_vus * 3,
110 },
111 Stage {
112 duration: format!("{}s", step_duration),
113 target: step_vus * 4,
114 },
115 Stage {
116 duration: format!("{}s", step_duration),
117 target: max_vus,
118 },
119 Stage {
120 duration: format!("{}s", step_duration),
121 target: 0,
122 },
123 ]
124 }
125 Self::Soak => {
126 let ramp_duration = duration_secs / 20;
128 let sustain_duration = duration_secs - (ramp_duration * 2);
129
130 vec![
131 Stage {
132 duration: format!("{}s", ramp_duration),
133 target: max_vus,
134 },
135 Stage {
136 duration: format!("{}s", sustain_duration),
137 target: max_vus,
138 },
139 Stage {
140 duration: format!("{}s", ramp_duration),
141 target: 0,
142 },
143 ]
144 }
145 }
146 }
147
148 pub fn description(&self) -> &str {
150 match self {
151 Self::Constant => "Constant load with steady VUs",
152 Self::RampUp => "Gradually increase load to target VUs",
153 Self::Spike => "Sudden spike in load to test system resilience",
154 Self::Stress => "Continuously increase load to find breaking point",
155 Self::Soak => "Sustained load over extended period",
156 }
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct Stage {
163 pub duration: String,
164 pub target: u32,
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::str::FromStr;
171
172 #[test]
173 fn test_scenario_from_str() {
174 assert_eq!(LoadScenario::from_str("constant").unwrap(), LoadScenario::Constant);
175 assert_eq!(LoadScenario::from_str("ramp-up").unwrap(), LoadScenario::RampUp);
176 assert_eq!(LoadScenario::from_str("spike").unwrap(), LoadScenario::Spike);
177 assert!(LoadScenario::from_str("unknown").is_err());
178 }
179
180 #[test]
181 fn test_constant_stages() {
182 let scenario = LoadScenario::Constant;
183 let stages = scenario.generate_stages(60, 10);
184 assert_eq!(stages.len(), 1);
185 assert_eq!(stages[0].target, 10);
186 }
187
188 #[test]
189 fn test_rampup_stages() {
190 let scenario = LoadScenario::RampUp;
191 let stages = scenario.generate_stages(120, 100);
192 assert!(stages.len() >= 3);
193 assert_eq!(stages.last().unwrap().target, 0);
194 }
195
196 #[test]
197 fn test_spike_stages() {
198 let scenario = LoadScenario::Spike;
199 let stages = scenario.generate_stages(100, 100);
200 assert!(stages.len() >= 3);
201 let max_stage = stages.iter().max_by_key(|s| s.target).unwrap();
203 assert_eq!(max_stage.target, 100);
204 }
205}