Readur/src/bin/analyze-webdav-performance.rs

394 lines
14 KiB
Rust

/*!
* 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<String>,
}
#[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<String, usize>,
loop_detection_stats: LoopDetectionStatistics,
}
#[derive(Debug, Serialize, Deserialize)]
struct StressTestReport {
test_suite_version: String,
test_timestamp: chrono::DateTime<chrono::Utc>,
overall_result: String,
test_summary: TestSummary,
recommendations: Vec<String>,
performance_metrics: Option<WebDAVPerformanceMetrics>,
}
#[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<String>,
warnings: Vec<String>,
recommendations: Vec<String>,
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::<String>("metrics-file").unwrap();
let output_format = matches.get_one::<String>("output-format").unwrap();
let output_file = matches.get_one::<String>("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<StressTestReport> {
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<PerformanceAnalysis> {
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<String> {
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<String> {
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<String> {
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("<details>\n");
summary.push_str("<summary>🚨 Critical Issues</summary>\n\n");
for issue in &analysis.critical_issues {
summary.push_str(&format!("- {}\n", issue));
}
summary.push_str("\n</details>\n\n");
}
// Warnings (collapsed section)
if !analysis.warnings.is_empty() {
summary.push_str("<details>\n");
summary.push_str("<summary>⚠️ Warnings</summary>\n\n");
for warning in &analysis.warnings {
summary.push_str(&format!("- {}\n", warning));
}
summary.push_str("\n</details>\n\n");
}
Ok(summary)
}