/*! * WebDAV Performance Analysis Tool * * Analyzes stress test metrics and generates comprehensive reports for CI/CD pipeline */ use anyhow::{anyhow, Result}; use clap::{Arg, Command}; use serde::{Deserialize, Serialize}; use std::fs; use std::path::Path; #[derive(Debug, Serialize, Deserialize)] struct LoopDetectionStatistics { total_directories_monitored: usize, total_directory_accesses: usize, suspected_loop_count: usize, max_accesses_per_directory: usize, average_accesses_per_directory: f64, suspected_directories: Vec, } #[derive(Debug, Serialize, Deserialize)] struct WebDAVPerformanceMetrics { total_operations: usize, successful_operations: usize, failed_operations: usize, average_operation_duration_ms: f64, max_operation_duration_ms: u64, min_operation_duration_ms: u64, timeout_count: usize, error_patterns: std::collections::HashMap, loop_detection_stats: LoopDetectionStatistics, } #[derive(Debug, Serialize, Deserialize)] struct StressTestReport { test_suite_version: String, test_timestamp: chrono::DateTime, overall_result: String, test_summary: TestSummary, recommendations: Vec, performance_metrics: Option, } #[derive(Debug, Serialize, Deserialize)] struct TestSummary { total_tests: usize, passed_tests: usize, failed_tests: usize, skipped_tests: usize, } #[derive(Debug)] struct PerformanceAnalysis { overall_health: HealthStatus, critical_issues: Vec, warnings: Vec, recommendations: Vec, metrics_summary: MetricsSummary, } #[derive(Debug)] enum HealthStatus { Healthy, Warning, Critical, Unknown, } #[derive(Debug)] struct MetricsSummary { success_rate: f64, average_response_time: f64, max_response_time: u64, timeout_rate: f64, loop_detection_triggered: bool, total_operations: usize, } fn main() -> Result<()> { let matches = Command::new("WebDAV Performance Analyzer") .version("1.0.0") .about("Analyzes WebDAV stress test metrics and generates reports") .arg( Arg::new("metrics-file") .long("metrics-file") .value_name("FILE") .help("Path to the stress test metrics JSON file") .required(true), ) .arg( Arg::new("output-format") .long("output-format") .value_name("FORMAT") .help("Output format: json, markdown, github-summary") .default_value("markdown"), ) .arg( Arg::new("output-file") .long("output-file") .value_name("FILE") .help("Output file path (stdout if not specified)"), ) .get_matches(); let metrics_file = matches.get_one::("metrics-file").unwrap(); let output_format = matches.get_one::("output-format").unwrap(); let output_file = matches.get_one::("output-file"); // Load and parse metrics file let report = load_stress_test_report(metrics_file)?; // Analyze performance metrics let analysis = analyze_performance(&report)?; // Generate output based on format let output_content = match output_format.as_str() { "json" => generate_json_report(&analysis)?, "markdown" => generate_markdown_report(&analysis, &report)?, "github-summary" => generate_github_summary(&analysis, &report)?, _ => return Err(anyhow!("Unsupported output format: {}", output_format)), }; // Write output if let Some(output_path) = output_file { fs::write(output_path, &output_content)?; println!("Report written to: {}", output_path); } else { println!("{}", output_content); } // Exit with appropriate code match analysis.overall_health { HealthStatus::Critical => std::process::exit(1), HealthStatus::Warning => std::process::exit(0), // Still success, but with warnings HealthStatus::Healthy => std::process::exit(0), HealthStatus::Unknown => std::process::exit(2), } } fn load_stress_test_report(file_path: &str) -> Result { if !Path::new(file_path).exists() { return Err(anyhow!("Metrics file not found: {}", file_path)); } let content = fs::read_to_string(file_path)?; let report: StressTestReport = serde_json::from_str(&content) .map_err(|e| anyhow!("Failed to parse metrics file: {}", e))?; Ok(report) } fn analyze_performance(report: &StressTestReport) -> Result { let mut critical_issues = Vec::new(); let mut warnings = Vec::new(); let mut recommendations = Vec::new(); let metrics_summary = if let Some(metrics) = &report.performance_metrics { let success_rate = if metrics.total_operations > 0 { (metrics.successful_operations as f64 / metrics.total_operations as f64) * 100.0 } else { 0.0 }; let timeout_rate = if metrics.total_operations > 0 { (metrics.timeout_count as f64 / metrics.total_operations as f64) * 100.0 } else { 0.0 }; // Analyze critical issues if success_rate < 50.0 { critical_issues.push(format!( "Critical: Very low success rate ({:.1}%) - indicates severe WebDAV connectivity issues", success_rate )); } else if success_rate < 80.0 { warnings.push(format!( "Warning: Low success rate ({:.1}%) - investigate WebDAV server performance", success_rate )); } if metrics.loop_detection_stats.suspected_loop_count > 0 { critical_issues.push(format!( "Critical: {} suspected infinite loops detected - immediate investigation required", metrics.loop_detection_stats.suspected_loop_count )); for dir in &metrics.loop_detection_stats.suspected_directories { critical_issues.push(format!(" - Suspected loop in directory: {}", dir)); } } if timeout_rate > 20.0 { critical_issues.push(format!( "Critical: High timeout rate ({:.1}%) - server may be overloaded or unresponsive", timeout_rate )); } else if timeout_rate > 10.0 { warnings.push(format!( "Warning: Elevated timeout rate ({:.1}%) - monitor server performance", timeout_rate )); } if metrics.average_operation_duration_ms > 5000.0 { warnings.push(format!( "Warning: Slow average response time ({:.1}ms) - consider server optimization", metrics.average_operation_duration_ms )); } // Generate recommendations if success_rate < 90.0 { recommendations.push("Consider increasing retry configuration for WebDAV operations".to_string()); } if timeout_rate > 5.0 { recommendations.push("Review WebDAV server timeout configuration and network stability".to_string()); } if metrics.loop_detection_stats.suspected_loop_count > 0 { recommendations.push("Implement additional safeguards against directory loop patterns".to_string()); recommendations.push("Review symlink handling and directory structure validation".to_string()); } if metrics.average_operation_duration_ms > 2000.0 { recommendations.push("Consider implementing caching strategies for frequently accessed directories".to_string()); } MetricsSummary { success_rate, average_response_time: metrics.average_operation_duration_ms, max_response_time: metrics.max_operation_duration_ms, timeout_rate, loop_detection_triggered: metrics.loop_detection_stats.suspected_loop_count > 0, total_operations: metrics.total_operations, } } else { warnings.push("Warning: No performance metrics available in the report".to_string()); MetricsSummary { success_rate: 0.0, average_response_time: 0.0, max_response_time: 0, timeout_rate: 0.0, loop_detection_triggered: false, total_operations: 0, } }; // Determine overall health let overall_health = if !critical_issues.is_empty() { HealthStatus::Critical } else if !warnings.is_empty() { HealthStatus::Warning } else if metrics_summary.total_operations > 0 { HealthStatus::Healthy } else { HealthStatus::Unknown }; Ok(PerformanceAnalysis { overall_health, critical_issues, warnings, recommendations, metrics_summary, }) } fn generate_json_report(analysis: &PerformanceAnalysis) -> Result { let json_report = serde_json::json!({ "overall_health": format!("{:?}", analysis.overall_health), "critical_issues": analysis.critical_issues, "warnings": analysis.warnings, "recommendations": analysis.recommendations, "metrics_summary": { "success_rate": analysis.metrics_summary.success_rate, "average_response_time_ms": analysis.metrics_summary.average_response_time, "max_response_time_ms": analysis.metrics_summary.max_response_time, "timeout_rate": analysis.metrics_summary.timeout_rate, "loop_detection_triggered": analysis.metrics_summary.loop_detection_triggered, "total_operations": analysis.metrics_summary.total_operations, } }); Ok(serde_json::to_string_pretty(&json_report)?) } fn generate_markdown_report(analysis: &PerformanceAnalysis, report: &StressTestReport) -> Result { let mut markdown = String::new(); markdown.push_str("# WebDAV Performance Analysis Report\n\n"); // Overall status markdown.push_str(&format!("**Overall Health Status:** {:?}\n\n", analysis.overall_health)); markdown.push_str(&format!("**Generated:** {}\n\n", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"))); // Test summary markdown.push_str("## Test Summary\n\n"); markdown.push_str(&format!("- **Total Tests:** {}\n", report.test_summary.total_tests)); markdown.push_str(&format!("- **Passed:** {}\n", report.test_summary.passed_tests)); markdown.push_str(&format!("- **Failed:** {}\n", report.test_summary.failed_tests)); markdown.push_str(&format!("- **Skipped:** {}\n\n", report.test_summary.skipped_tests)); // Performance metrics markdown.push_str("## Performance Metrics\n\n"); markdown.push_str(&format!("- **Success Rate:** {:.1}%\n", analysis.metrics_summary.success_rate)); markdown.push_str(&format!("- **Average Response Time:** {:.1}ms\n", analysis.metrics_summary.average_response_time)); markdown.push_str(&format!("- **Max Response Time:** {}ms\n", analysis.metrics_summary.max_response_time)); markdown.push_str(&format!("- **Timeout Rate:** {:.1}%\n", analysis.metrics_summary.timeout_rate)); markdown.push_str(&format!("- **Total Operations:** {}\n", analysis.metrics_summary.total_operations)); markdown.push_str(&format!("- **Loop Detection Triggered:** {}\n\n", analysis.metrics_summary.loop_detection_triggered)); // Critical issues if !analysis.critical_issues.is_empty() { markdown.push_str("## 🚨 Critical Issues\n\n"); for issue in &analysis.critical_issues { markdown.push_str(&format!("- {}\n", issue)); } markdown.push_str("\n"); } // Warnings if !analysis.warnings.is_empty() { markdown.push_str("## ⚠️ Warnings\n\n"); for warning in &analysis.warnings { markdown.push_str(&format!("- {}\n", warning)); } markdown.push_str("\n"); } // Recommendations if !analysis.recommendations.is_empty() { markdown.push_str("## 💡 Recommendations\n\n"); for recommendation in &analysis.recommendations { markdown.push_str(&format!("- {}\n", recommendation)); } markdown.push_str("\n"); } // Write to file for GitHub Actions fs::write("webdav-performance-report.md", &markdown)?; Ok(markdown) } fn generate_github_summary(analysis: &PerformanceAnalysis, report: &StressTestReport) -> Result { let mut summary = String::new(); // Status icon based on health let status_icon = match analysis.overall_health { HealthStatus::Healthy => "✅", HealthStatus::Warning => "⚠️", HealthStatus::Critical => "🚨", HealthStatus::Unknown => "❓", }; summary.push_str(&format!("{} **WebDAV Stress Test Results**\n\n", status_icon)); // Quick stats table summary.push_str("| Metric | Value |\n"); summary.push_str("|--------|-------|\n"); summary.push_str(&format!("| Success Rate | {:.1}% |\n", analysis.metrics_summary.success_rate)); summary.push_str(&format!("| Total Operations | {} |\n", analysis.metrics_summary.total_operations)); summary.push_str(&format!("| Avg Response Time | {:.1}ms |\n", analysis.metrics_summary.average_response_time)); summary.push_str(&format!("| Timeout Rate | {:.1}% |\n", analysis.metrics_summary.timeout_rate)); summary.push_str(&format!("| Loop Detection | {} |\n", if analysis.metrics_summary.loop_detection_triggered { "⚠️ TRIGGERED" } else { "✅ OK" })); summary.push_str("\n"); // Critical issues (collapsed section) if !analysis.critical_issues.is_empty() { summary.push_str("
\n"); summary.push_str("🚨 Critical Issues\n\n"); for issue in &analysis.critical_issues { summary.push_str(&format!("- {}\n", issue)); } summary.push_str("\n
\n\n"); } // Warnings (collapsed section) if !analysis.warnings.is_empty() { summary.push_str("
\n"); summary.push_str("⚠️ Warnings\n\n"); for warning in &analysis.warnings { summary.push_str(&format!("- {}\n", warning)); } summary.push_str("\n
\n\n"); } Ok(summary) }