Skip to main content

mockforge_http/handlers/
performance.rs

1//! Performance Mode API handlers
2//!
3//! Provides HTTP endpoints for performance mode:
4//! - Start/stop performance simulation
5//! - Configure RPS profiles
6//! - Add/remove bottlenecks
7//! - Get performance metrics and snapshots
8
9use 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/// State for performance mode handlers
28#[derive(Clone)]
29pub struct PerformanceState {
30    /// Performance simulator
31    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    /// Create new performance state
42    pub fn new() -> Self {
43        Self {
44            simulator: Arc::new(RwLock::new(None)),
45        }
46    }
47}
48
49/// Request to start performance mode
50#[derive(Debug, Deserialize)]
51pub struct StartPerformanceRequest {
52    /// Initial RPS
53    pub initial_rps: f64,
54    /// RPS profile (optional)
55    pub rps_profile: Option<RpsProfile>,
56    /// Bottlenecks (optional)
57    pub bottlenecks: Option<Vec<BottleneckConfig>>,
58}
59
60/// Request to update RPS
61#[derive(Debug, Deserialize)]
62pub struct UpdateRpsRequest {
63    /// Target RPS
64    pub target_rps: f64,
65}
66
67/// Request to add bottleneck
68#[derive(Debug, Deserialize)]
69pub struct AddBottleneckRequest {
70    /// Bottleneck configuration
71    pub bottleneck: BottleneckConfig,
72}
73
74/// Response for performance operations
75#[derive(Debug, Serialize)]
76pub struct PerformanceResponse {
77    /// Whether the operation was successful
78    pub success: bool,
79    /// Response message
80    pub message: String,
81    /// Performance snapshot (if available)
82    pub snapshot: Option<PerformanceSnapshot>,
83}
84
85/// Start performance mode
86/// POST /api/performance/start
87pub 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
124/// Stop performance mode
125/// POST /api/performance/stop
126pub 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
148/// Get current performance snapshot
149/// GET /api/performance/snapshot
150pub 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
166/// Update target RPS
167/// POST /api/performance/rps
168pub 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
191/// Add bottleneck
192/// POST /api/performance/bottlenecks
193pub 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
216/// Remove all bottlenecks
217/// DELETE /api/performance/bottlenecks
218pub 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
240/// Get performance status
241/// GET /api/performance/status
242pub 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
274/// Create the Axum router for performance mode API
275pub 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}