use std::time::{Duration, Instant};
use serde::{Deserialize, Serialize};
use crate::core::{Plugin, PluginContext, PluginResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginPerformanceMetrics {
pub plugin_name: String,
pub total_time: Duration,
pub init_time: Duration,
pub execute_time: Duration,
pub cleanup_time: Duration,
pub iterations: u32,
pub avg_time_per_iteration: Duration,
pub min_time: Duration,
pub max_time: Duration,
pub std_deviation: f64,
pub memory_metrics: Option<MemoryMetrics>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryMetrics {
pub peak_memory: u64,
pub allocated_memory: u64,
pub deallocated_memory: u64,
}
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
pub iterations: u32,
pub warmup_iterations: u32,
pub timeout: Duration,
pub collect_memory_metrics: bool,
pub parallel: bool,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
iterations: 100,
warmup_iterations: 10,
timeout: Duration::from_secs(30),
collect_memory_metrics: false,
parallel: false,
}
}
}
pub struct PluginBenchmark {
config: BenchmarkConfig,
results: Vec<PluginPerformanceMetrics>,
}
impl PluginBenchmark {
pub fn new(config: BenchmarkConfig) -> Self {
Self {
config,
results: Vec::new(),
}
}
pub async fn benchmark_plugin<P: Plugin>(
&mut self,
mut plugin: P,
context: &PluginContext,
config: serde_json::Value,
) -> PluginResult<PluginPerformanceMetrics> {
let plugin_name = plugin.metadata().name.clone();
let mut execution_times = Vec::new();
let mut init_times = Vec::new();
let mut execute_times = Vec::new();
let mut cleanup_times = Vec::new();
for _ in 0..self.config.warmup_iterations {
let _ = self
.run_single_iteration(&mut plugin, context, &config)
.await?;
}
for _ in 0..self.config.iterations {
let iteration_metrics = self
.run_single_iteration(&mut plugin, context, &config)
.await?;
execution_times.push(iteration_metrics.total_time);
init_times.push(iteration_metrics.init_time);
execute_times.push(iteration_metrics.execute_time);
cleanup_times.push(iteration_metrics.cleanup_time);
}
let total_time = execution_times.iter().sum::<Duration>() / execution_times.len() as u32;
let init_time = init_times.iter().sum::<Duration>() / init_times.len() as u32;
let execute_time = execute_times.iter().sum::<Duration>() / execute_times.len() as u32;
let cleanup_time = cleanup_times.iter().sum::<Duration>() / cleanup_times.len() as u32;
let min_time = *execution_times.iter().min().unwrap();
let max_time = *execution_times.iter().max().unwrap();
let avg_nanos = execution_times
.iter()
.map(|d| d.as_nanos() as f64)
.sum::<f64>()
/ execution_times.len() as f64;
let variance = execution_times
.iter()
.map(|d| {
let diff = d.as_nanos() as f64 - avg_nanos;
diff * diff
})
.sum::<f64>()
/ execution_times.len() as f64;
let std_deviation = variance.sqrt();
let metrics = PluginPerformanceMetrics {
plugin_name,
total_time,
init_time,
execute_time,
cleanup_time,
iterations: self.config.iterations,
avg_time_per_iteration: total_time,
min_time,
max_time,
std_deviation,
memory_metrics: None, };
self.results.push(metrics.clone());
Ok(metrics)
}
async fn run_single_iteration<P: Plugin>(
&self,
plugin: &mut P,
context: &PluginContext,
config: &serde_json::Value,
) -> PluginResult<PluginPerformanceMetrics> {
let start_time = Instant::now();
let init_start = Instant::now();
plugin.initialize(config.clone(), context).await?;
let init_time = init_start.elapsed();
let execute_start = Instant::now();
let mut context_clone = context.clone();
let _output = plugin.execute(&mut context_clone).await?;
let execute_time = execute_start.elapsed();
let cleanup_start = Instant::now();
plugin.cleanup(context).await?;
let cleanup_time = cleanup_start.elapsed();
let total_time = start_time.elapsed();
Ok(PluginPerformanceMetrics {
plugin_name: plugin.metadata().name.clone(),
total_time,
init_time,
execute_time,
cleanup_time,
iterations: 1,
avg_time_per_iteration: total_time,
min_time: total_time,
max_time: total_time,
std_deviation: 0.0,
memory_metrics: None,
})
}
pub fn get_results(&self) -> &[PluginPerformanceMetrics] {
&self.results
}
pub fn generate_report(&self) -> PerformanceReport {
PerformanceReport::new(&self.results)
}
pub fn compare_plugins(
metrics1: &PluginPerformanceMetrics,
metrics2: &PluginPerformanceMetrics,
) -> PluginComparison {
let speedup = metrics1.avg_time_per_iteration.as_nanos() as f64
/ metrics2.avg_time_per_iteration.as_nanos() as f64;
PluginComparison {
plugin1: metrics1.plugin_name.clone(),
plugin2: metrics2.plugin_name.clone(),
speedup,
faster_plugin: if speedup > 1.0 {
metrics2.plugin_name.clone()
} else {
metrics1.plugin_name.clone()
},
time_difference: metrics1
.avg_time_per_iteration
.abs_diff(metrics2.avg_time_per_iteration),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginComparison {
pub plugin1: String,
pub plugin2: String,
pub speedup: f64,
pub faster_plugin: String,
pub time_difference: Duration,
}
#[derive(Debug, Clone)]
pub struct PerformanceReport {
metrics: Vec<PluginPerformanceMetrics>,
}
impl PerformanceReport {
pub fn new(metrics: &[PluginPerformanceMetrics]) -> Self {
Self {
metrics: metrics.to_vec(),
}
}
pub fn to_text(&self) -> String {
let mut report = String::new();
report.push_str("Plugin Performance Report\n");
report.push_str("=========================\n\n");
for metric in &self.metrics {
report.push_str(&format!("Plugin: {}\n", metric.plugin_name));
report.push_str(&format!(" Iterations: {}\n", metric.iterations));
report.push_str(&format!(
" Average Time: {:?}\n",
metric.avg_time_per_iteration
));
report.push_str(&format!(" Min Time: {:?}\n", metric.min_time));
report.push_str(&format!(" Max Time: {:?}\n", metric.max_time));
report.push_str(&format!(" Std Deviation: {:.2}ns\n", metric.std_deviation));
report.push_str(&format!(" Init Time: {:?}\n", metric.init_time));
report.push_str(&format!(" Execute Time: {:?}\n", metric.execute_time));
report.push_str(&format!(" Cleanup Time: {:?}\n", metric.cleanup_time));
report.push('\n');
}
report
}
pub fn to_json(&self) -> PluginResult<String> {
serde_json::to_string_pretty(&self.metrics)
.map_err(|e| crate::core::PluginError::SerializationError(e.to_string()))
}
pub fn fastest_plugin(&self) -> Option<&PluginPerformanceMetrics> {
self.metrics.iter().min_by_key(|m| m.avg_time_per_iteration)
}
pub fn slowest_plugin(&self) -> Option<&PluginPerformanceMetrics> {
self.metrics.iter().max_by_key(|m| m.avg_time_per_iteration)
}
pub fn overall_stats(&self) -> Option<OverallStats> {
if self.metrics.is_empty() {
return None;
}
let total_plugins = self.metrics.len();
let avg_execution_time = self
.metrics
.iter()
.map(|m| m.avg_time_per_iteration.as_nanos())
.sum::<u128>()
/ total_plugins as u128;
Some(OverallStats {
total_plugins,
avg_execution_time: Duration::from_nanos(avg_execution_time as u64),
fastest_plugin: self.fastest_plugin().map(|m| m.plugin_name.clone()),
slowest_plugin: self.slowest_plugin().map(|m| m.plugin_name.clone()),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverallStats {
pub total_plugins: usize,
pub avg_execution_time: Duration,
pub fastest_plugin: Option<String>,
pub slowest_plugin: Option<String>,
}
pub mod perf_test_utils {
use super::*;
use crate::core::testing::MockPlugin;
pub fn quick_benchmark_config() -> BenchmarkConfig {
BenchmarkConfig {
iterations: 10,
warmup_iterations: 2,
timeout: Duration::from_secs(5),
collect_memory_metrics: false,
parallel: false,
}
}
pub fn thorough_benchmark_config() -> BenchmarkConfig {
BenchmarkConfig {
iterations: 1000,
warmup_iterations: 100,
timeout: Duration::from_secs(30),
collect_memory_metrics: true,
parallel: false,
}
}
pub fn delayed_mock_plugin(name: &str, delay_ms: u64) -> MockPlugin {
MockPlugin::new(name, "1.0.0").with_execute(move |_| {
std::thread::sleep(Duration::from_millis(delay_ms));
Ok(crate::core::PluginOutput::success(serde_json::json!({
"delay_ms": delay_ms
})))
})
}
pub fn cpu_intensive_plugin(name: &str, iterations: u64) -> MockPlugin {
MockPlugin::new(name, "1.0.0").with_execute(move |_| {
let mut sum = 0u64;
for i in 0..iterations {
sum = sum.wrapping_add(i * i);
}
Ok(crate::core::PluginOutput::success(serde_json::json!({
"cpu_work_result": sum,
"iterations": iterations
})))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{testing::MockPlugin, PluginContext};
use tempfile::TempDir;
#[tokio::test]
async fn test_plugin_benchmark_basic() {
let temp_dir = TempDir::new().unwrap();
let context = PluginContext::new("benchmark-test", temp_dir.path().to_path_buf());
let plugin = MockPlugin::new("benchmark-plugin", "1.0.0");
let config = BenchmarkConfig {
iterations: 5,
warmup_iterations: 1,
..Default::default()
};
let mut benchmark = PluginBenchmark::new(config);
let metrics = benchmark
.benchmark_plugin(plugin, &context, serde_json::json!({}))
.await
.unwrap();
assert_eq!(metrics.plugin_name, "benchmark-plugin");
assert_eq!(metrics.iterations, 5);
assert!(metrics.total_time > Duration::from_nanos(0));
}
#[tokio::test]
async fn test_performance_comparison() {
let temp_dir = TempDir::new().unwrap();
let context = PluginContext::new("comparison-test", temp_dir.path().to_path_buf());
let fast_plugin = perf_test_utils::delayed_mock_plugin("fast", 1);
let slow_plugin = perf_test_utils::delayed_mock_plugin("slow", 10);
let config = perf_test_utils::quick_benchmark_config();
let mut benchmark = PluginBenchmark::new(config);
let fast_metrics = benchmark
.benchmark_plugin(fast_plugin, &context, serde_json::json!({}))
.await
.unwrap();
let slow_metrics = benchmark
.benchmark_plugin(slow_plugin, &context, serde_json::json!({}))
.await
.unwrap();
let comparison = PluginBenchmark::compare_plugins(&slow_metrics, &fast_metrics);
assert_eq!(comparison.faster_plugin, "fast");
assert!(comparison.speedup > 1.0);
}
#[test]
fn test_performance_report() {
let metrics = vec![
PluginPerformanceMetrics {
plugin_name: "plugin1".to_string(),
total_time: Duration::from_millis(100),
init_time: Duration::from_millis(10),
execute_time: Duration::from_millis(80),
cleanup_time: Duration::from_millis(10),
iterations: 10,
avg_time_per_iteration: Duration::from_millis(100),
min_time: Duration::from_millis(90),
max_time: Duration::from_millis(110),
std_deviation: 5.0,
memory_metrics: None,
},
PluginPerformanceMetrics {
plugin_name: "plugin2".to_string(),
total_time: Duration::from_millis(200),
init_time: Duration::from_millis(20),
execute_time: Duration::from_millis(160),
cleanup_time: Duration::from_millis(20),
iterations: 10,
avg_time_per_iteration: Duration::from_millis(200),
min_time: Duration::from_millis(180),
max_time: Duration::from_millis(220),
std_deviation: 10.0,
memory_metrics: None,
},
];
let report = PerformanceReport::new(&metrics);
assert_eq!(report.fastest_plugin().unwrap().plugin_name, "plugin1");
assert_eq!(report.slowest_plugin().unwrap().plugin_name, "plugin2");
let stats = report.overall_stats().unwrap();
assert_eq!(stats.total_plugins, 2);
assert_eq!(stats.fastest_plugin, Some("plugin1".to_string()));
assert_eq!(stats.slowest_plugin, Some("plugin2".to_string()));
let text_report = report.to_text();
assert!(text_report.contains("Plugin: plugin1"));
assert!(text_report.contains("Plugin: plugin2"));
}
}