bevy_debugger_mcp/
system_profiler_processor.rs1use crate::brp_messages::{DebugCommand, DebugResponse};
3use crate::debug_command_processor::DebugCommandProcessor;
4use crate::error::{Error, Result};
5use crate::system_profiler::{SystemProfiler, ExportFormat};
6use async_trait::async_trait;
7use std::sync::Arc;
8use std::time::Duration;
9use tracing::info;
10
11pub struct SystemProfilerProcessor {
13 profiler: Arc<SystemProfiler>,
15}
16
17impl SystemProfilerProcessor {
18 pub fn new(profiler: Arc<SystemProfiler>) -> Self {
20 Self { profiler }
21 }
22}
23
24#[async_trait]
25impl DebugCommandProcessor for SystemProfilerProcessor {
26 async fn process(&self, command: DebugCommand) -> Result<DebugResponse> {
27 match command {
28 DebugCommand::ProfileSystem {
29 system_name,
30 duration_ms,
31 track_allocations,
32 } => {
33 info!("Processing ProfileSystem command for: {}", system_name);
34
35 self.profiler
37 .start_profiling(system_name.clone(), duration_ms, track_allocations)
38 .await?;
39
40 if let Some(duration) = duration_ms {
42 if duration <= 5000 {
44 tokio::time::sleep(Duration::from_millis(duration)).await;
45 let profile = self.profiler.stop_profiling(&system_name).await?;
46 return Ok(DebugResponse::SystemProfile(profile));
47 }
48 }
49
50 Ok(DebugResponse::ProfilingStarted {
52 system_name,
53 duration_ms,
54 })
55 }
56 _ => Err(Error::DebugError("Unsupported command for SystemProfilerProcessor".to_string())),
57 }
58 }
59
60 async fn validate(&self, command: &DebugCommand) -> Result<()> {
61 match command {
62 DebugCommand::ProfileSystem { system_name, duration_ms, .. } => {
63 if system_name.is_empty() {
65 return Err(Error::DebugError("System name cannot be empty".to_string()));
66 }
67
68 if system_name.len() > 256 {
69 return Err(Error::DebugError("System name too long (max 256 chars)".to_string()));
70 }
71
72 if let Some(duration) = duration_ms {
74 if *duration == 0 {
75 return Err(Error::DebugError("Duration must be greater than 0".to_string()));
76 }
77 if *duration > 300_000 { return Err(Error::DebugError("Duration too long (max 5 minutes)".to_string()));
79 }
80 }
81
82 Ok(())
83 }
84 _ => Ok(()),
85 }
86 }
87
88 fn estimate_processing_time(&self, command: &DebugCommand) -> Duration {
89 match command {
90 DebugCommand::ProfileSystem { duration_ms, .. } => {
91 Duration::from_millis(duration_ms.unwrap_or(5000))
93 }
94 _ => Duration::from_millis(10),
95 }
96 }
97
98 fn supports_command(&self, command: &DebugCommand) -> bool {
99 matches!(command, DebugCommand::ProfileSystem { .. })
100 }
101}
102
103pub struct ExtendedProfilerProcessor {
105 profiler: Arc<SystemProfiler>,
107}
108
109impl ExtendedProfilerProcessor {
110 pub fn new(profiler: Arc<SystemProfiler>) -> Self {
112 Self { profiler }
113 }
114
115 pub async fn stop_profiling(&self, system_name: &str) -> Result<DebugResponse> {
117 let profile = self.profiler.stop_profiling(system_name).await?;
118 Ok(DebugResponse::SystemProfile(profile))
119 }
120
121 pub async fn get_history(&self, system_name: &str) -> Result<DebugResponse> {
123 let samples = self.profiler.get_system_history(system_name).await;
124
125 let frame_count = samples.len();
127 Ok(DebugResponse::ProfileHistory {
128 system_name: system_name.to_string(),
129 samples,
130 frame_count,
131 })
132 }
133
134 pub async fn get_anomalies(&self) -> Result<DebugResponse> {
136 let anomalies = self.profiler.get_anomalies().await;
137
138 Ok(DebugResponse::PerformanceAnomalies {
139 count: anomalies.len(),
140 anomalies: anomalies.into_iter().map(|a| {
141 serde_json::json!({
142 "frame": a.frame_number,
143 "system": a.system_name,
144 "execution_ms": a.execution_time_ms,
145 "expected_ms": a.expected_time_ms,
146 "severity": a.severity,
147 "detected_at_us": a.detected_at_us,
148 })
149 }).collect(),
150 })
151 }
152
153 pub async fn export_data(&self, format: ExportFormat) -> Result<String> {
155 self.profiler.export_profiling_data(format).await
156 }
157
158 pub async fn clear_data(&self) -> Result<()> {
160 self.profiler.clear_profiling_data().await;
161 Ok(())
162 }
163
164 pub async fn update_dependencies(
166 &self,
167 system_name: String,
168 dependencies: Vec<String>,
169 ) -> Result<()> {
170 self.profiler.update_dependency_graph(system_name, dependencies).await;
171 Ok(())
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::brp_client::BrpClient;
179 use crate::config::Config;
180 use tokio::sync::RwLock;
181
182 #[tokio::test]
183 async fn test_profiler_processor_creation() {
184 let config = Config {
185 bevy_brp_host: "localhost".to_string(),
186 bevy_brp_port: 15702,
187 mcp_port: 3000,
188 };
189 let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
190 let profiler = Arc::new(SystemProfiler::new(brp_client));
191 let processor = SystemProfilerProcessor::new(profiler);
192
193 let command = DebugCommand::ProfileSystem {
194 system_name: "test".to_string(),
195 duration_ms: Some(1000),
196 track_allocations: Some(false),
197 };
198
199 assert!(processor.supports_command(&command));
200 }
201
202 #[tokio::test]
203 async fn test_command_validation() {
204 let config = Config {
205 bevy_brp_host: "localhost".to_string(),
206 bevy_brp_port: 15702,
207 mcp_port: 3000,
208 };
209 let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
210 let profiler = Arc::new(SystemProfiler::new(brp_client));
211 let processor = SystemProfilerProcessor::new(profiler);
212
213 let valid_command = DebugCommand::ProfileSystem {
215 system_name: "test_system".to_string(),
216 duration_ms: Some(1000),
217 track_allocations: Some(false),
218 };
219 assert!(processor.validate(&valid_command).await.is_ok());
220
221 let invalid_command = DebugCommand::ProfileSystem {
223 system_name: "".to_string(),
224 duration_ms: Some(1000),
225 track_allocations: Some(false),
226 };
227 assert!(processor.validate(&invalid_command).await.is_err());
228
229 let invalid_command = DebugCommand::ProfileSystem {
231 system_name: "test".to_string(),
232 duration_ms: Some(400_000),
233 track_allocations: Some(false),
234 };
235 assert!(processor.validate(&invalid_command).await.is_err());
236 }
237
238 #[test]
239 fn test_processing_time_estimation() {
240 let config = Config {
241 bevy_brp_host: "localhost".to_string(),
242 bevy_brp_port: 15702,
243 mcp_port: 3000,
244 };
245 let brp_client = Arc::new(RwLock::new(BrpClient::new(&config)));
246 let profiler = Arc::new(SystemProfiler::new(brp_client));
247 let processor = SystemProfilerProcessor::new(profiler);
248
249 let command = DebugCommand::ProfileSystem {
250 system_name: "test".to_string(),
251 duration_ms: Some(3000),
252 track_allocations: Some(false),
253 };
254
255 let estimated_time = processor.estimate_processing_time(&command);
256 assert_eq!(estimated_time.as_millis(), 3000);
257 }
258}