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::{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/// 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 PerformanceState {
35    /// Create new performance state
36    pub fn new() -> Self {
37        Self {
38            simulator: Arc::new(RwLock::new(None)),
39        }
40    }
41}
42
43/// Request to start performance mode
44#[derive(Debug, Deserialize)]
45pub struct StartPerformanceRequest {
46    /// Initial RPS
47    pub initial_rps: f64,
48    /// RPS profile (optional)
49    pub rps_profile: Option<RpsProfile>,
50    /// Bottlenecks (optional)
51    pub bottlenecks: Option<Vec<BottleneckConfig>>,
52}
53
54/// Request to update RPS
55#[derive(Debug, Deserialize)]
56pub struct UpdateRpsRequest {
57    /// Target RPS
58    pub target_rps: f64,
59}
60
61/// Request to add bottleneck
62#[derive(Debug, Deserialize)]
63pub struct AddBottleneckRequest {
64    /// Bottleneck configuration
65    pub bottleneck: BottleneckConfig,
66}
67
68/// Response for performance operations
69#[derive(Debug, Serialize)]
70pub struct PerformanceResponse {
71    /// Whether the operation was successful
72    pub success: bool,
73    /// Response message
74    pub message: String,
75    /// Performance snapshot (if available)
76    pub snapshot: Option<PerformanceSnapshot>,
77}
78
79/// Start performance mode
80/// POST /api/performance/start
81pub 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
118/// Stop performance mode
119/// POST /api/performance/stop
120pub 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
142/// Get current performance snapshot
143/// GET /api/performance/snapshot
144pub 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
160/// Update target RPS
161/// POST /api/performance/rps
162pub 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
185/// Add bottleneck
186/// POST /api/performance/bottlenecks
187pub 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
210/// Remove all bottlenecks
211/// DELETE /api/performance/bottlenecks
212pub 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
234/// Get performance status
235/// GET /api/performance/status
236pub 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
268/// Create the Axum router for performance mode API
269pub 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}