use std::sync::Arc;
use std::time::Duration;
use std::collections::HashMap;
use serde_json::json;
use bevy_debugger_mcp::{
config::Config,
mcp_server::McpServer,
brp_client::BrpClient,
lazy_init::LazyComponents,
command_cache::{CommandCache, CacheConfig},
response_pool::{ResponsePool, ResponsePoolConfig},
};
mod helpers;
mod fixtures;
mod integration;
use helpers::{
PerformanceMeasurement, PerformanceTargets, RegressionDetector,
MemoryUsageTracker, generate_realistic_queries, generate_workload_pattern,
TestGameProcess, with_test_game,
};
use integration::IntegrationTestHarness;
#[derive(Debug, Clone)]
pub struct PerformanceBaseline {
pub name: String,
pub measurements: HashMap<String, Duration>,
pub memory_baseline: u64,
pub throughput_baseline: f64,
pub created_at: std::time::SystemTime,
}
impl PerformanceBaseline {
pub fn from_measurement(name: &str, measurement: &PerformanceMeasurement, memory_tracker: &MemoryUsageTracker) -> Self {
let summary = measurement.performance_summary();
let mut measurements = HashMap::new();
for (op_name, stats) in &summary.operation_stats {
measurements.insert(op_name.clone(), stats.p99_duration);
}
Self {
name: name.to_string(),
measurements,
memory_baseline: memory_tracker.current_usage(),
throughput_baseline: summary.throughput_ops_per_sec,
created_at: std::time::SystemTime::now(),
}
}
pub fn save_to_file(&self, path: &str) -> std::io::Result<()> {
use std::fs::File;
use std::io::Write;
let serialized = serde_json::to_string_pretty(self)?;
let mut file = File::create(path)?;
file.write_all(serialized.as_bytes())?;
Ok(())
}
pub fn load_from_file(path: &str) -> std::io::Result<Self> {
use std::fs::File;
use std::io::Read;
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let baseline: Self = serde_json::from_str(&contents)?;
Ok(baseline)
}
}
impl serde::Serialize for PerformanceBaseline {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("PerformanceBaseline", 5)?;
state.serialize_field("name", &self.name)?;
let measurements_ms: HashMap<String, f64> = self.measurements
.iter()
.map(|(k, v)| (k.clone(), v.as_millis() as f64))
.collect();
state.serialize_field("measurements", &measurements_ms)?;
state.serialize_field("memory_baseline", &self.memory_baseline)?;
state.serialize_field("throughput_baseline", &self.throughput_baseline)?;
state.serialize_field("created_at", &self.created_at.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs())?;
state.end()
}
}
impl<'de> serde::Deserialize<'de> for PerformanceBaseline {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use std::fmt;
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field { Name, Measurements, MemoryBaseline, ThroughputBaseline, CreatedAt }
struct PerformanceBaselineVisitor;
impl<'de> Visitor<'de> for PerformanceBaselineVisitor {
type Value = PerformanceBaseline;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct PerformanceBaseline")
}
fn visit_map<V>(self, mut map: V) -> Result<PerformanceBaseline, V::Error>
where
V: MapAccess<'de>,
{
let mut name = None;
let mut measurements_ms: Option<HashMap<String, f64>> = None;
let mut memory_baseline = None;
let mut throughput_baseline = None;
let mut created_at_secs = None;
while let Some(key) = map.next_key()? {
match key {
Field::Name => {
if name.is_some() {
return Err(de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
}
Field::Measurements => {
if measurements_ms.is_some() {
return Err(de::Error::duplicate_field("measurements"));
}
measurements_ms = Some(map.next_value()?);
}
Field::MemoryBaseline => {
if memory_baseline.is_some() {
return Err(de::Error::duplicate_field("memory_baseline"));
}
memory_baseline = Some(map.next_value()?);
}
Field::ThroughputBaseline => {
if throughput_baseline.is_some() {
return Err(de::Error::duplicate_field("throughput_baseline"));
}
throughput_baseline = Some(map.next_value()?);
}
Field::CreatedAt => {
if created_at_secs.is_some() {
return Err(de::Error::duplicate_field("created_at"));
}
created_at_secs = Some(map.next_value()?);
}
}
}
let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
let measurements_ms = measurements_ms.ok_or_else(|| de::Error::missing_field("measurements"))?;
let memory_baseline = memory_baseline.ok_or_else(|| de::Error::missing_field("memory_baseline"))?;
let throughput_baseline = throughput_baseline.ok_or_else(|| de::Error::missing_field("throughput_baseline"))?;
let created_at_secs = created_at_secs.ok_or_else(|| de::Error::missing_field("created_at"))?;
let measurements: HashMap<String, Duration> = measurements_ms
.into_iter()
.map(|(k, ms)| (k, Duration::from_millis(ms as u64)))
.collect();
let created_at = std::time::UNIX_EPOCH + Duration::from_secs(created_at_secs);
Ok(PerformanceBaseline {
name,
measurements,
memory_baseline,
throughput_baseline,
created_at,
})
}
}
const FIELDS: &'static [&'static str] = &["name", "measurements", "memory_baseline", "throughput_baseline", "created_at"];
deserializer.deserialize_struct("PerformanceBaseline", FIELDS, PerformanceBaselineVisitor)
}
}
#[tokio::test]
async fn test_establish_performance_baselines() {
let result = with_test_game("performance_test_game", |mut game_process| async move {
let mut performance = PerformanceMeasurement::with_targets(
"Baseline Establishment",
PerformanceTargets::bevdbg_012_targets()
);
let mut memory_tracker = MemoryUsageTracker::new();
memory_tracker.record_measurement();
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let lazy_components = LazyComponents::new(brp_client.clone());
let cache = CommandCache::new(CacheConfig::default());
let pool = ResponsePool::new(ResponsePoolConfig::default());
let mcp_server = Arc::new(McpServer::new(config, brp_client));
memory_tracker.record_measurement();
let baseline_operations = [
("lazy_init_entity_inspector", || async {
lazy_components.get_entity_inspector().await
}),
("lazy_init_system_profiler", || async {
lazy_components.get_system_profiler().await
}),
("mcp_health_check", || {
let server = mcp_server.clone();
async move {
server.handle_tool_call("health_check", json!({})).await
}
}),
("mcp_resource_metrics", || {
let server = mcp_server.clone();
async move {
server.handle_tool_call("resource_metrics", json!({})).await
}
}),
("mcp_observe_entities", || {
let server = mcp_server.clone();
async move {
server.handle_tool_call("observe", json!({"query": "entities with Transform"})).await
}
}),
("cache_set_operation", || async {
let test_data = json!({"baseline": "cache_test"});
cache.set("baseline_test", &json!({"arg": "value"}), test_data).await;
}),
("cache_get_operation", || async {
cache.get("baseline_test", &json!({"arg": "value"})).await
}),
("response_pool_small", || async {
pool.serialize_json(&json!({"size": "small", "data": [1, 2, 3]})).await
}),
("response_pool_large", || async {
let large_data: Vec<i32> = (0..1000).collect();
pool.serialize_json(&json!({"size": "large", "data": large_data})).await
}),
];
for (op_name, op_closure) in baseline_operations {
for i in 0..10 {
performance.measure_async(&format!("{}_{}", op_name, i), op_closure).await;
if i % 3 == 0 {
memory_tracker.record_measurement();
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
let realistic_queries = generate_realistic_queries();
for (i, (tool_name, args)) in realistic_queries.iter().take(15).enumerate() {
performance.measure_async(&format!("realistic_{}_{}", tool_name, i), || {
let server = mcp_server.clone();
let tool_name = tool_name.clone();
let args = args.clone();
async move {
server.handle_tool_call(&tool_name, args).await
}
}).await;
}
memory_tracker.record_measurement();
let baseline = PerformanceBaseline::from_measurement(
"BEVDBG-012 Optimization Baseline",
&performance,
&memory_tracker
);
let baseline_path = "/tmp/bevdbg_012_baseline.json";
baseline.save_to_file(baseline_path).expect("Should save baseline");
println!("=== Performance Baseline Established ===");
println!("Baseline name: {}", baseline.name);
println!("Operations measured: {}", baseline.measurements.len());
println!("Memory baseline: {:.2} MB", baseline.memory_baseline as f64 / 1_048_576.0);
println!("Throughput baseline: {:.2} ops/sec", baseline.throughput_baseline);
println!("Saved to: {}", baseline_path);
assert!(performance.meets_targets(),
"Baseline should meet performance targets");
Ok(baseline)
}).await;
assert!(result.is_ok(), "Baseline establishment should succeed");
}
#[tokio::test]
async fn test_regression_detection_with_degraded_performance() {
let mut baseline_performance = PerformanceMeasurement::new("Baseline");
for i in 0..10 {
baseline_performance.record(&format!("operation_{}", i), Duration::from_millis(5));
}
baseline_performance.record("critical_operation", Duration::from_millis(2));
let baseline_summary = baseline_performance.performance_summary();
let mut degraded_performance = PerformanceMeasurement::new("Degraded");
for i in 0..8 {
degraded_performance.record(&format!("operation_{}", i), Duration::from_millis(7));
}
degraded_performance.record("operation_8", Duration::from_millis(25)); degraded_performance.record("operation_9", Duration::from_millis(15)); degraded_performance.record("critical_operation", Duration::from_millis(12)); let degraded_summary = degraded_performance.performance_summary();
let mut detector = RegressionDetector::new(15.0); detector.set_baseline(&baseline_summary);
let report = detector.check_regression(°raded_summary);
println!("=== Regression Detection Test ===");
println!("{}", report.generate_report());
assert!(report.has_regressions(), "Should detect regressions");
assert!(report.regressions.len() >= 3, "Should detect at least 3 regressions");
let critical_regression = report.regressions.iter()
.find(|r| r.operation_name == "critical_operation");
assert!(critical_regression.is_some(), "Should detect critical operation regression");
let critical_regression = critical_regression.unwrap();
assert!(critical_regression.change_percent > 400.0,
"Critical operation should show >400% regression");
println!("Regression detection test passed: ✓");
}
#[tokio::test]
async fn test_performance_with_optimization_configurations() {
let configurations = [
("no_optimizations", false, false, false),
("cache_only", true, false, false),
("pool_only", false, true, false),
("lazy_only", false, false, true),
("all_optimizations", true, true, true),
];
let mut config_results = HashMap::new();
for (config_name, use_cache, use_pool, use_lazy) in configurations {
let mut performance = PerformanceMeasurement::with_targets(
config_name,
PerformanceTargets::testing_targets()
);
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let mcp_server = Arc::new(McpServer::new(config, brp_client.clone()));
let _cache = if use_cache {
Some(CommandCache::new(CacheConfig::default()))
} else {
None
};
let _pool = if use_pool {
Some(ResponsePool::new(ResponsePoolConfig::default()))
} else {
None
};
let _lazy_components = if use_lazy {
Some(LazyComponents::new(brp_client.clone()))
} else {
None
};
let test_operations = [
("health_check", json!({})),
("resource_metrics", json!({})),
("observe", json!({"query": "entities with Transform"})),
("observe", json!({"query": "entities with Health < 50"})),
];
for (op_name, args) in test_operations {
for i in 0..5 {
performance.measure_async(&format!("{}_{}", op_name, i), || {
let server = mcp_server.clone();
async move {
server.handle_tool_call(op_name, args.clone()).await
}
}).await;
}
}
let summary = performance.performance_summary();
config_results.insert(config_name.to_string(), summary);
println!("Configuration '{}' completed - Throughput: {:.2} ops/sec",
config_name, summary.throughput_ops_per_sec);
}
println!("\n=== Optimization Configuration Comparison ===");
let baseline_throughput = config_results.get("no_optimizations")
.map(|s| s.throughput_ops_per_sec)
.unwrap_or(1.0);
for (config_name, summary) in &config_results {
let speedup = summary.throughput_ops_per_sec / baseline_throughput;
println!("Config '{}': {:.2} ops/sec ({:.1}x speedup)",
config_name, summary.throughput_ops_per_sec, speedup);
}
let all_opts_throughput = config_results.get("all_optimizations")
.map(|s| s.throughput_ops_per_sec)
.unwrap_or(0.0);
assert!(all_opts_throughput > baseline_throughput * 1.5,
"All optimizations should provide at least 1.5x speedup");
println!("Optimization configuration testing passed: ✓");
}
#[tokio::test]
async fn test_performance_stability_short_term() {
let mut performance = PerformanceMeasurement::with_targets(
"Stability Test",
PerformanceTargets::testing_targets()
);
let mut memory_tracker = MemoryUsageTracker::new();
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let mcp_server = Arc::new(McpServer::new(config, brp_client));
let test_duration = Duration::from_secs(30);
let start_time = std::time::Instant::now();
let mut iteration = 0;
while start_time.elapsed() < test_duration {
iteration += 1;
let workload_pattern = match iteration % 3 {
0 => "debugging_session",
1 => "performance_analysis",
_ => "cache_warming",
};
let queries = generate_workload_pattern(workload_pattern);
for (tool_name, args) in queries.into_iter().take(3) { performance.measure_async(&format!("stability_{}_{}", iteration, tool_name), || {
let server = mcp_server.clone();
async move {
server.handle_tool_call(&tool_name, args).await
}
}).await;
}
if iteration % 10 == 0 {
memory_tracker.record_measurement();
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
let summary = performance.performance_summary();
println!("=== Short-term Stability Test Results ===");
println!("{}", performance.generate_report());
println!("{}", memory_tracker.generate_report());
assert!(memory_tracker.is_memory_stable(1_000_000.0), "Memory should remain stable");
assert!(summary.throughput_ops_per_sec > 5.0,
"Should maintain reasonable throughput");
for (op_name, stats) in &summary.operation_stats {
assert!(stats.p99_duration.as_millis() < 1000,
"Operation {} should complete within 1s", op_name);
}
println!("Short-term stability test passed: ✓");
}
#[tokio::test]
async fn test_optimization_consistency_across_scenarios() {
let scenarios = [
("light_load", 5, 100), ("medium_load", 15, 50), ("heavy_load", 25, 20), ];
let mut scenario_results = HashMap::new();
for (scenario_name, op_count, interval_ms) in scenarios {
let mut performance = PerformanceMeasurement::with_targets(
scenario_name,
PerformanceTargets::testing_targets()
);
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let mcp_server = Arc::new(McpServer::new(config, brp_client));
let realistic_queries = generate_realistic_queries();
for (i, (tool_name, args)) in realistic_queries.iter().take(op_count).enumerate() {
performance.measure_async(&format!("{}_{}", tool_name, i), || {
let server = mcp_server.clone();
let tool_name = tool_name.clone();
let args = args.clone();
async move {
server.handle_tool_call(&tool_name, args).await
}
}).await;
tokio::time::sleep(Duration::from_millis(interval_ms)).await;
}
let summary = performance.performance_summary();
scenario_results.insert(scenario_name, summary);
println!("Scenario '{}': {:.2} ops/sec", scenario_name, summary.throughput_ops_per_sec);
}
println!("\n=== Optimization Consistency Analysis ===");
let mut all_p99_latencies = Vec::new();
for (scenario_name, summary) in &scenario_results {
println!("Scenario '{}':", scenario_name);
for (op_name, stats) in &summary.operation_stats {
let p99_ms = stats.p99_duration.as_millis() as f64;
all_p99_latencies.push(p99_ms);
assert!(p99_ms < 100.0,
"Operation {} in scenario {} should be < 100ms, got {:.2}ms",
op_name, scenario_name, p99_ms);
}
}
let mean_latency: f64 = all_p99_latencies.iter().sum::<f64>() / all_p99_latencies.len() as f64;
let variance: f64 = all_p99_latencies.iter()
.map(|x| (x - mean_latency).powi(2))
.sum::<f64>() / all_p99_latencies.len() as f64;
let std_dev = variance.sqrt();
let coefficient_of_variation = std_dev / mean_latency;
println!("Mean P99 latency: {:.2}ms", mean_latency);
println!("Standard deviation: {:.2}ms", std_dev);
println!("Coefficient of variation: {:.3}", coefficient_of_variation);
assert!(coefficient_of_variation < 0.5,
"Performance should be consistent across scenarios (CV: {:.3})",
coefficient_of_variation);
println!("Optimization consistency test passed: ✓");
}
#[tokio::test]
async fn test_cache_performance_impact() {
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let cache = CommandCache::new(CacheConfig::default());
let mcp_server = Arc::new(McpServer::new(config, brp_client));
let mut no_cache_performance = PerformanceMeasurement::new("No Cache");
let cache_test_queries = [
("observe", json!({"query": "entities with Transform"})),
("observe", json!({"query": "entities with Health"})),
("resource_metrics", json!({})),
];
for (tool_name, args) in &cache_test_queries {
for i in 0..5 {
no_cache_performance.measure_async(&format!("cold_{}_{}", tool_name, i), || {
let server = mcp_server.clone();
let tool_name = tool_name.clone();
let args = args.clone();
async move {
server.handle_tool_call(&tool_name, args).await
}
}).await;
}
}
for (tool_name, args) in &cache_test_queries {
let _ = cache.set(tool_name, args, json!({"cached": "data"})).await;
}
let mut cache_performance = PerformanceMeasurement::new("With Cache");
for (tool_name, args) in &cache_test_queries {
for i in 0..5 {
cache_performance.measure_async(&format!("warm_{}_{}", tool_name, i), || async {
let _ = cache.get(tool_name, args).await;
}).await;
}
}
let no_cache_summary = no_cache_performance.performance_summary();
let cache_summary = cache_performance.performance_summary();
println!("=== Cache Performance Impact Analysis ===");
println!("Without cache throughput: {:.2} ops/sec", no_cache_summary.throughput_ops_per_sec);
println!("With cache throughput: {:.2} ops/sec", cache_summary.throughput_ops_per_sec);
let cache_speedup = cache_summary.throughput_ops_per_sec / no_cache_summary.throughput_ops_per_sec;
println!("Cache speedup: {:.1}x", cache_speedup);
assert!(cache_speedup > 5.0,
"Cache should provide at least 5x speedup, got {:.2}x", cache_speedup);
println!("Cache performance impact test passed: ✓");
}
#[tokio::test]
async fn test_memory_regression_detection() {
let config = Config {
bevy_brp_host: "localhost".to_string(),
bevy_brp_port: 15702,
mcp_port: 3001,
};
let mut baseline_tracker = MemoryUsageTracker::new();
baseline_tracker.record_measurement();
let brp_client = Arc::new(tokio::sync::RwLock::new(BrpClient::new(&config)));
let mcp_server = Arc::new(McpServer::new(config, brp_client));
baseline_tracker.record_measurement();
for i in 0..10 {
let _ = mcp_server.handle_tool_call(
"health_check",
json!({"iteration": i})
).await;
if i % 3 == 0 {
baseline_tracker.record_measurement();
}
}
let baseline_memory = baseline_tracker.current_usage();
let baseline_overhead = baseline_tracker.current_overhead();
let mut regression_tracker = MemoryUsageTracker::new();
regression_tracker.record_measurement();
for i in 0..20 {
let large_query = json!({
"query": format!("large query with data {}", "x".repeat(i * 100)),
"include_large_data": true
});
let _ = mcp_server.handle_tool_call("observe", large_query).await;
if i % 2 == 0 {
regression_tracker.record_measurement();
}
}
let regression_memory = regression_tracker.current_usage();
let regression_overhead = regression_tracker.current_overhead();
println!("=== Memory Regression Detection ===");
println!("Baseline memory: {:.2} MB", baseline_memory as f64 / 1_048_576.0);
println!("Regression memory: {:.2} MB", regression_memory as f64 / 1_048_576.0);
println!("Baseline overhead: {:.2} MB", baseline_overhead as f64 / 1_048_576.0);
println!("Regression overhead: {:.2} MB", regression_overhead as f64 / 1_048_576.0);
let memory_growth = regression_memory as f64 / baseline_memory as f64;
let overhead_growth = regression_overhead as f64 / baseline_overhead.max(1) as f64;
println!("Memory growth ratio: {:.2}x", memory_growth);
println!("Overhead growth ratio: {:.2}x", overhead_growth);
assert!(memory_growth < 3.0,
"Memory growth should be reasonable, got {:.2}x", memory_growth);
assert!(regression_overhead < 50_000_000, "Memory overhead should stay within limits");
assert!(regression_tracker.is_memory_stable(2_000_000.0), "Memory should be stable during operations");
println!("Memory regression detection test passed: ✓");
}