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