mockforge_http/handlers/
performance.rs1use axum::{
10 extract::State,
11 http::StatusCode,
12 response::Json,
13 routing::{delete, get, post},
14 Router,
15};
16use mockforge_performance::{
17 bottleneck::BottleneckConfig,
18 controller::RpsProfile,
19 metrics::PerformanceSnapshot,
20 simulator::{PerformanceSimulator, SimulatorConfig},
21};
22use serde::{Deserialize, Serialize};
23use std::sync::Arc;
24use tokio::sync::RwLock;
25use tracing::info;
26
27#[derive(Clone)]
29pub struct PerformanceState {
30 pub simulator: Arc<RwLock<Option<Arc<PerformanceSimulator>>>>,
32}
33
34impl Default for PerformanceState {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl PerformanceState {
41 pub fn new() -> Self {
43 Self {
44 simulator: Arc::new(RwLock::new(None)),
45 }
46 }
47}
48
49#[derive(Debug, Deserialize)]
51pub struct StartPerformanceRequest {
52 pub initial_rps: f64,
54 pub rps_profile: Option<RpsProfile>,
56 pub bottlenecks: Option<Vec<BottleneckConfig>>,
58}
59
60#[derive(Debug, Deserialize)]
62pub struct UpdateRpsRequest {
63 pub target_rps: f64,
65}
66
67#[derive(Debug, Deserialize)]
69pub struct AddBottleneckRequest {
70 pub bottleneck: BottleneckConfig,
72}
73
74#[derive(Debug, Serialize)]
76pub struct PerformanceResponse {
77 pub success: bool,
79 pub message: String,
81 pub snapshot: Option<PerformanceSnapshot>,
83}
84
85pub async fn start_performance(
88 State(state): State<PerformanceState>,
89 Json(request): Json<StartPerformanceRequest>,
90) -> Result<Json<PerformanceResponse>, StatusCode> {
91 info!("Starting performance mode with RPS: {}", request.initial_rps);
92
93 let config = SimulatorConfig::new(request.initial_rps).with_rps_profile(
94 request.rps_profile.unwrap_or_else(|| RpsProfile::constant(request.initial_rps)),
95 );
96
97 let config = if let Some(bottlenecks) = request.bottlenecks {
98 let mut cfg = config;
99 for bottleneck in bottlenecks {
100 cfg = cfg.with_bottleneck(bottleneck);
101 }
102 cfg
103 } else {
104 config
105 };
106
107 let simulator = Arc::new(PerformanceSimulator::new(config));
108 simulator.start().await;
109
110 {
111 let mut sim = state.simulator.write().await;
112 *sim = Some(simulator.clone());
113 }
114
115 let snapshot = simulator.get_snapshot().await;
116
117 Ok(Json(PerformanceResponse {
118 success: true,
119 message: "Performance mode started".to_string(),
120 snapshot: Some(snapshot),
121 }))
122}
123
124pub async fn stop_performance(
127 State(state): State<PerformanceState>,
128) -> Result<Json<PerformanceResponse>, StatusCode> {
129 info!("Stopping performance mode");
130
131 let simulator = {
132 let mut sim = state.simulator.write().await;
133 sim.take()
134 };
135
136 if let Some(sim) = simulator {
137 sim.stop().await;
138 Ok(Json(PerformanceResponse {
139 success: true,
140 message: "Performance mode stopped".to_string(),
141 snapshot: None,
142 }))
143 } else {
144 Err(StatusCode::NOT_FOUND)
145 }
146}
147
148pub async fn get_performance_snapshot(
151 State(state): State<PerformanceState>,
152) -> Result<Json<PerformanceSnapshot>, StatusCode> {
153 let simulator = {
154 let sim = state.simulator.read().await;
155 sim.clone()
156 };
157
158 if let Some(sim) = simulator {
159 let snapshot = sim.get_snapshot().await;
160 Ok(Json(snapshot))
161 } else {
162 Err(StatusCode::NOT_FOUND)
163 }
164}
165
166pub async fn update_rps(
169 State(state): State<PerformanceState>,
170 Json(request): Json<UpdateRpsRequest>,
171) -> Result<Json<PerformanceResponse>, StatusCode> {
172 let simulator = {
173 let sim = state.simulator.read().await;
174 sim.clone()
175 };
176
177 if let Some(sim) = simulator {
178 sim.rps_controller().set_target_rps(request.target_rps).await;
179 let snapshot = sim.get_snapshot().await;
180
181 Ok(Json(PerformanceResponse {
182 success: true,
183 message: format!("RPS updated to {}", request.target_rps),
184 snapshot: Some(snapshot),
185 }))
186 } else {
187 Err(StatusCode::NOT_FOUND)
188 }
189}
190
191pub async fn add_bottleneck(
194 State(state): State<PerformanceState>,
195 Json(request): Json<AddBottleneckRequest>,
196) -> Result<Json<PerformanceResponse>, StatusCode> {
197 let simulator = {
198 let sim = state.simulator.read().await;
199 sim.clone()
200 };
201
202 if let Some(sim) = simulator {
203 sim.bottleneck_simulator().add_bottleneck(request.bottleneck).await;
204 let snapshot = sim.get_snapshot().await;
205
206 Ok(Json(PerformanceResponse {
207 success: true,
208 message: "Bottleneck added".to_string(),
209 snapshot: Some(snapshot),
210 }))
211 } else {
212 Err(StatusCode::NOT_FOUND)
213 }
214}
215
216pub async fn clear_bottlenecks(
219 State(state): State<PerformanceState>,
220) -> Result<Json<PerformanceResponse>, StatusCode> {
221 let simulator = {
222 let sim = state.simulator.read().await;
223 sim.clone()
224 };
225
226 if let Some(sim) = simulator {
227 sim.bottleneck_simulator().clear_bottlenecks().await;
228 let snapshot = sim.get_snapshot().await;
229
230 Ok(Json(PerformanceResponse {
231 success: true,
232 message: "All bottlenecks cleared".to_string(),
233 snapshot: Some(snapshot),
234 }))
235 } else {
236 Err(StatusCode::NOT_FOUND)
237 }
238}
239
240pub async fn get_performance_status(
243 State(state): State<PerformanceState>,
244) -> Json<serde_json::Value> {
245 let simulator = {
246 let sim = state.simulator.read().await;
247 sim.clone()
248 };
249
250 if let Some(sim) = simulator {
251 let is_running = sim.is_running().await;
252 let target_rps = sim.rps_controller().get_target_rps().await;
253 let current_rps = sim.rps_controller().get_current_rps().await;
254 let bottlenecks = sim.bottleneck_simulator().get_bottlenecks().await;
255
256 Json(serde_json::json!({
257 "running": is_running,
258 "target_rps": target_rps,
259 "current_rps": current_rps,
260 "bottlenecks": bottlenecks.len(),
261 "bottleneck_types": bottlenecks.iter().map(|b| format!("{:?}", b.bottleneck_type)).collect::<Vec<_>>(),
262 }))
263 } else {
264 Json(serde_json::json!({
265 "running": false,
266 "target_rps": 0.0,
267 "current_rps": 0.0,
268 "bottlenecks": 0,
269 "bottleneck_types": Vec::<String>::new(),
270 }))
271 }
272}
273
274pub fn performance_router(state: PerformanceState) -> Router {
276 Router::new()
277 .route("/start", post(start_performance))
278 .route("/stop", post(stop_performance))
279 .route("/snapshot", get(get_performance_snapshot))
280 .route("/rps", post(update_rps))
281 .route("/bottlenecks", post(add_bottleneck))
282 .route("/bottlenecks", delete(clear_bottlenecks))
283 .route("/status", get(get_performance_status))
284 .with_state(state)
285}