- fix(workspace): exclude crates/training/rtx-distributed from workspace members — RNCCL path deps absent in standalone checkout blocked all cargo operations - refactor(rtx-backend-webgpu): split compute.rs (1654 lines) into compute/mod.rs (1040) + compute/conv.rs (628) — both within 1250-line limit - fix(rtx-bench): add missing src/bin/main.rs declared in [[bin]] Cargo.toml entry - fix(gitignore): narrow `bin/` exclusion to /bin/ only; add !**/src/bin/ exception to allow Rust source binary directories - style(rtx-eval): 67x "literal".to_string() → "literal".to_owned() in automation, validation, metrics, lib, core, error modules and build.rs All tests pass (64 tests across rtx-eval + rtx-backend-webgpu, 0 failures). Clippy clean (-D warnings) on all changed crates. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
828 lines
26 KiB
Rust
828 lines
26 KiB
Rust
//! Automated CI/CD benchmarking system for RTX-Eval
|
|
//!
|
|
//! Provides comprehensive automation for continuous benchmarking including:
|
|
//! - CI/CD pipeline integration
|
|
//! - Regression detection and alerting
|
|
//! - Performance tracking over time
|
|
//! - Automated competitor comparisons
|
|
//! - Scheduled benchmark execution
|
|
|
|
use crate::core::BenchmarkResults;
|
|
use crate::error::RTXEvalResult;
|
|
use crate::validation::CompetitorFramework;
|
|
use crate::{BenchmarkCategory, EvalConfig, RTXEvaluator};
|
|
use anyhow::Result;
|
|
use dashmap::DashMap;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
use tokio::time::{Duration, interval};
|
|
use tracing::{error, info};
|
|
|
|
// Helper macro for creating hashmaps
|
|
macro_rules! hashmap {
|
|
($($key:expr => $value:expr),*) => {
|
|
{
|
|
let mut map = HashMap::new();
|
|
$(map.insert($key, $value);)*
|
|
map
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Automated benchmark orchestrator
|
|
#[derive(Debug)]
|
|
pub struct BenchmarkAutomation {
|
|
config: AutomationConfig,
|
|
scheduler: Arc<BenchmarkScheduler>,
|
|
regression_detector: Arc<RegressionDetector>,
|
|
performance_tracker: Arc<PerformanceTracker>,
|
|
alerting_system: Arc<AlertingSystem>,
|
|
competitor_analyzer: Arc<CompetitorAnalyzer>,
|
|
}
|
|
|
|
/// Configuration for automation system
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AutomationConfig {
|
|
/// Enable CI/CD integration
|
|
pub ci_integration: bool,
|
|
/// Benchmark scheduling interval
|
|
pub schedule_interval: Duration,
|
|
/// Regression detection threshold (percentage)
|
|
pub regression_threshold: f64,
|
|
/// Maximum number of historical results to keep
|
|
pub max_history: usize,
|
|
/// Enable alerts
|
|
pub enable_alerts: bool,
|
|
/// Alert channels (email, slack, etc.)
|
|
pub alert_channels: Vec<AlertChannel>,
|
|
/// Enable competitor comparisons
|
|
pub competitor_analysis: bool,
|
|
/// Results storage directory
|
|
pub results_directory: PathBuf,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum AlertChannel {
|
|
Email { address: String },
|
|
Slack { webhook: String },
|
|
Teams { webhook: String },
|
|
GitHub { token: String, repo: String },
|
|
}
|
|
|
|
/// Benchmark scheduler for automated execution
|
|
#[derive(Debug)]
|
|
pub struct BenchmarkScheduler {
|
|
config: AutomationConfig,
|
|
scheduled_jobs: Arc<DashMap<String, ScheduledJob>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ScheduledJob {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub categories: Vec<BenchmarkCategory>,
|
|
pub schedule: Schedule,
|
|
pub next_run: chrono::DateTime<chrono::Utc>,
|
|
pub last_run: Option<chrono::DateTime<chrono::Utc>>,
|
|
pub enabled: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum Schedule {
|
|
Interval(Duration),
|
|
Cron(String),
|
|
OnPush,
|
|
OnPR,
|
|
Manual,
|
|
}
|
|
|
|
/// Regression detection system
|
|
#[derive(Debug)]
|
|
pub struct RegressionDetector {
|
|
config: AutomationConfig,
|
|
historical_results: Arc<RwLock<HashMap<String, Vec<HistoricalResult>>>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HistoricalResult {
|
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
pub benchmark_name: String,
|
|
pub category: BenchmarkCategory,
|
|
pub performance_score: f64,
|
|
pub accuracy: f64,
|
|
pub throughput: f64,
|
|
pub git_commit: Option<String>,
|
|
pub rtx_version: String,
|
|
}
|
|
|
|
/// Performance tracking over time
|
|
#[derive(Debug)]
|
|
pub struct PerformanceTracker {
|
|
config: AutomationConfig,
|
|
metrics_storage: Arc<RwLock<HashMap<String, Vec<PerformancePoint>>>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PerformancePoint {
|
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
pub benchmark: String,
|
|
pub metric_name: String,
|
|
pub value: f64,
|
|
pub baseline_comparison: f64, // Percentage change from baseline
|
|
}
|
|
|
|
/// Alerting system for regression notifications
|
|
#[derive(Debug)]
|
|
pub struct AlertingSystem {
|
|
config: AutomationConfig,
|
|
alert_history: Arc<RwLock<Vec<Alert>>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Alert {
|
|
pub id: String,
|
|
pub severity: AlertSeverity,
|
|
pub title: String,
|
|
pub message: String,
|
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
pub benchmark_name: String,
|
|
pub regression_percentage: f64,
|
|
pub channels_notified: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum AlertSeverity {
|
|
Info,
|
|
Warning,
|
|
Critical,
|
|
Emergency,
|
|
}
|
|
|
|
/// Competitor analysis system
|
|
#[derive(Debug)]
|
|
pub struct CompetitorAnalyzer {
|
|
config: AutomationConfig,
|
|
competitor_baselines: Arc<RwLock<HashMap<String, CompetitorBaseline>>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CompetitorBaseline {
|
|
pub name: String,
|
|
pub framework: CompetitorFramework,
|
|
pub benchmarks: HashMap<String, f64>,
|
|
pub last_updated: chrono::DateTime<chrono::Utc>,
|
|
pub hardware: String,
|
|
pub version: String,
|
|
}
|
|
|
|
impl BenchmarkAutomation {
|
|
/// Create a new automation system
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
let scheduler = Arc::new(BenchmarkScheduler::new(config.clone())?);
|
|
let regression_detector = Arc::new(RegressionDetector::new(config.clone())?);
|
|
let performance_tracker = Arc::new(PerformanceTracker::new(config.clone())?);
|
|
let alerting_system = Arc::new(AlertingSystem::new(config.clone())?);
|
|
let competitor_analyzer = Arc::new(CompetitorAnalyzer::new(config.clone())?);
|
|
|
|
Ok(Self {
|
|
config,
|
|
scheduler,
|
|
regression_detector,
|
|
performance_tracker,
|
|
alerting_system,
|
|
competitor_analyzer,
|
|
})
|
|
}
|
|
|
|
/// Start the automation system
|
|
pub async fn start(&self) -> RTXEvalResult<()> {
|
|
info!("Starting RTX-Eval automation system");
|
|
|
|
if self.config.ci_integration {
|
|
self.setup_ci_integration().await?;
|
|
}
|
|
|
|
// Start scheduler
|
|
self.start_scheduler().await?;
|
|
|
|
// Start regression monitoring
|
|
self.start_regression_monitoring().await?;
|
|
|
|
info!("RTX-Eval automation system started successfully");
|
|
Ok(())
|
|
}
|
|
|
|
/// Setup CI/CD integration
|
|
async fn setup_ci_integration(&self) -> RTXEvalResult<()> {
|
|
info!("Setting up CI/CD integration");
|
|
|
|
// Register benchmark jobs for different triggers
|
|
self.register_ci_jobs().await?;
|
|
|
|
// Setup webhook handlers for GitHub/GitLab
|
|
self.setup_webhook_handlers().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Register CI benchmark jobs
|
|
async fn register_ci_jobs(&self) -> RTXEvalResult<()> {
|
|
// Full benchmark suite for main branch pushes
|
|
self.scheduler
|
|
.schedule_job(ScheduledJob {
|
|
id: "ci-full-suite".to_owned(),
|
|
name: "Full CI Benchmark Suite".to_owned(),
|
|
categories: vec![
|
|
BenchmarkCategory::Language,
|
|
BenchmarkCategory::Vision,
|
|
BenchmarkCategory::Multimodal,
|
|
BenchmarkCategory::Scientific,
|
|
BenchmarkCategory::Performance,
|
|
],
|
|
schedule: Schedule::OnPush,
|
|
next_run: chrono::Utc::now(),
|
|
last_run: None,
|
|
enabled: true,
|
|
})
|
|
.await?;
|
|
|
|
// Quick performance check for PRs
|
|
self.scheduler
|
|
.schedule_job(ScheduledJob {
|
|
id: "ci-pr-check".to_owned(),
|
|
name: "PR Performance Check".to_owned(),
|
|
categories: vec![BenchmarkCategory::Performance],
|
|
schedule: Schedule::OnPR,
|
|
next_run: chrono::Utc::now(),
|
|
last_run: None,
|
|
enabled: true,
|
|
})
|
|
.await?;
|
|
|
|
// Nightly comprehensive evaluation
|
|
self.scheduler
|
|
.schedule_job(ScheduledJob {
|
|
id: "nightly-comprehensive".to_owned(),
|
|
name: "Nightly Comprehensive Evaluation".to_owned(),
|
|
categories: vec![
|
|
BenchmarkCategory::Language,
|
|
BenchmarkCategory::Vision,
|
|
BenchmarkCategory::Multimodal,
|
|
BenchmarkCategory::Scientific,
|
|
BenchmarkCategory::Performance,
|
|
BenchmarkCategory::Robustness,
|
|
],
|
|
schedule: Schedule::Cron("0 2 * * *".to_owned()), // 2 AM daily
|
|
next_run: chrono::Utc::now() + chrono::Duration::hours(24),
|
|
last_run: None,
|
|
enabled: true,
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Setup webhook handlers for CI integration
|
|
async fn setup_webhook_handlers(&self) -> RTXEvalResult<()> {
|
|
// This would setup actual webhook endpoints in a real implementation
|
|
info!("Webhook handlers configured for CI integration");
|
|
Ok(())
|
|
}
|
|
|
|
/// Start the benchmark scheduler
|
|
async fn start_scheduler(&self) -> RTXEvalResult<()> {
|
|
let scheduler = Arc::clone(&self.scheduler);
|
|
|
|
tokio::spawn(async move {
|
|
let mut interval = interval(Duration::from_secs(60)); // Check every minute
|
|
|
|
loop {
|
|
interval.tick().await;
|
|
|
|
if let Err(e) = scheduler.check_and_run_jobs().await {
|
|
error!("Scheduler error: {}", e);
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Start regression monitoring
|
|
async fn start_regression_monitoring(&self) -> RTXEvalResult<()> {
|
|
let regression_detector = Arc::clone(&self.regression_detector);
|
|
let alerting_system = Arc::clone(&self.alerting_system);
|
|
|
|
tokio::spawn(async move {
|
|
let mut interval = interval(Duration::from_secs(300)); // Check every 5 minutes
|
|
|
|
loop {
|
|
interval.tick().await;
|
|
|
|
match regression_detector.check_for_regressions().await {
|
|
Ok(regressions) => {
|
|
for regression in regressions {
|
|
if let Err(e) = alerting_system.send_regression_alert(regression).await
|
|
{
|
|
error!("Failed to send regression alert: {}", e);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("Regression detection error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Execute benchmarks triggered by CI events
|
|
pub async fn execute_ci_benchmarks(
|
|
&self,
|
|
trigger: CiTrigger,
|
|
) -> RTXEvalResult<BenchmarkResults> {
|
|
info!("Executing CI benchmarks for trigger: {:?}", trigger);
|
|
|
|
let categories = match trigger {
|
|
CiTrigger::Push => vec![
|
|
BenchmarkCategory::Performance,
|
|
BenchmarkCategory::Language,
|
|
BenchmarkCategory::Vision,
|
|
],
|
|
CiTrigger::PullRequest => vec![BenchmarkCategory::Performance],
|
|
CiTrigger::Schedule => vec![
|
|
BenchmarkCategory::Language,
|
|
BenchmarkCategory::Vision,
|
|
BenchmarkCategory::Multimodal,
|
|
BenchmarkCategory::Scientific,
|
|
BenchmarkCategory::Performance,
|
|
BenchmarkCategory::Robustness,
|
|
],
|
|
};
|
|
|
|
let eval_config = EvalConfig {
|
|
categories,
|
|
timeout: Duration::from_secs(1800), // 30 minutes for CI
|
|
..Default::default()
|
|
};
|
|
|
|
let mut evaluator = RTXEvaluator::with_config(eval_config)?;
|
|
let results = evaluator.run_comprehensive_evaluation().await?;
|
|
|
|
// Store results for historical tracking
|
|
self.store_benchmark_results(&results).await?;
|
|
|
|
// Check for regressions
|
|
self.check_and_report_regressions(&results).await?;
|
|
|
|
Ok(BenchmarkResults {
|
|
category: BenchmarkCategory::Performance, // Primary category for CI
|
|
results: results
|
|
.results
|
|
.values()
|
|
.flat_map(|r| r.results.clone())
|
|
.collect(),
|
|
summary: crate::core::BenchmarkSummary {
|
|
total_benchmarks: results.summary.total_benchmarks_run,
|
|
successful_benchmarks: results.summary.total_benchmarks_run,
|
|
failed_benchmarks: 0,
|
|
total_duration: Duration::from_secs(300),
|
|
average_accuracy: results.summary.average_accuracy,
|
|
performance_score: results.summary.average_performance_improvement,
|
|
},
|
|
timestamp: chrono::Utc::now(),
|
|
})
|
|
}
|
|
|
|
/// Store benchmark results for historical analysis
|
|
async fn store_benchmark_results(
|
|
&self,
|
|
results: &crate::EvaluationReport,
|
|
) -> RTXEvalResult<()> {
|
|
for benchmark_results in results.results.values() {
|
|
for result in &benchmark_results.results {
|
|
let historical_result = HistoricalResult {
|
|
timestamp: result.start_time,
|
|
benchmark_name: result.benchmark_name.clone(),
|
|
category: result.category,
|
|
performance_score: result.metrics.get("efficiency").copied().unwrap_or(0.0),
|
|
accuracy: result.metrics.get("accuracy").copied().unwrap_or(0.0),
|
|
throughput: result.metrics.get("throughput").copied().unwrap_or(0.0),
|
|
git_commit: None, // Would be populated in real CI
|
|
rtx_version: results.rtx_version.clone(),
|
|
};
|
|
|
|
self.regression_detector
|
|
.add_result(historical_result)
|
|
.await?;
|
|
self.performance_tracker
|
|
.add_performance_point(result)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Check and report regressions
|
|
async fn check_and_report_regressions(
|
|
&self,
|
|
results: &crate::EvaluationReport,
|
|
) -> RTXEvalResult<()> {
|
|
let regressions = self.regression_detector.detect_regressions(results).await?;
|
|
|
|
for regression in regressions {
|
|
self.alerting_system
|
|
.send_regression_alert(regression)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Generate automation report
|
|
pub async fn generate_automation_report(&self) -> RTXEvalResult<AutomationReport> {
|
|
let job_status = self.scheduler.get_job_status().await?;
|
|
let recent_alerts = self.alerting_system.get_recent_alerts().await?;
|
|
let performance_trends = self.performance_tracker.get_performance_trends().await?;
|
|
let competitor_comparison = self.competitor_analyzer.get_latest_comparison().await?;
|
|
|
|
Ok(AutomationReport {
|
|
timestamp: chrono::Utc::now(),
|
|
job_status,
|
|
recent_alerts,
|
|
performance_trends,
|
|
competitor_comparison,
|
|
system_health: SystemHealth {
|
|
scheduler_active: true,
|
|
regression_detector_active: true,
|
|
alerting_active: true,
|
|
last_successful_run: chrono::Utc::now(),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum CiTrigger {
|
|
Push,
|
|
PullRequest,
|
|
Schedule,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct AutomationReport {
|
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
pub job_status: HashMap<String, JobStatus>,
|
|
pub recent_alerts: Vec<Alert>,
|
|
pub performance_trends: HashMap<String, PerformanceTrend>,
|
|
pub competitor_comparison: Option<CompetitorComparison>,
|
|
pub system_health: SystemHealth,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct JobStatus {
|
|
pub name: String,
|
|
pub last_run: Option<chrono::DateTime<chrono::Utc>>,
|
|
pub next_run: chrono::DateTime<chrono::Utc>,
|
|
pub success_rate: f64,
|
|
pub average_duration: Duration,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct PerformanceTrend {
|
|
pub benchmark_name: String,
|
|
pub trend: TrendDirection,
|
|
pub change_percentage: f64,
|
|
pub confidence: f64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum TrendDirection {
|
|
Improving,
|
|
Stable,
|
|
Degrading,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct CompetitorComparison {
|
|
pub rtx_advantage: HashMap<String, f64>,
|
|
pub last_updated: chrono::DateTime<chrono::Utc>,
|
|
pub confidence_interval: (f64, f64),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SystemHealth {
|
|
pub scheduler_active: bool,
|
|
pub regression_detector_active: bool,
|
|
pub alerting_active: bool,
|
|
pub last_successful_run: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
// Implementation of individual components
|
|
|
|
impl BenchmarkScheduler {
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
config,
|
|
scheduled_jobs: Arc::new(DashMap::new()),
|
|
})
|
|
}
|
|
|
|
pub async fn schedule_job(&self, job: ScheduledJob) -> RTXEvalResult<()> {
|
|
info!("Scheduling job: {}", job.name);
|
|
self.scheduled_jobs.insert(job.id.clone(), job);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn check_and_run_jobs(&self) -> RTXEvalResult<()> {
|
|
let now = chrono::Utc::now();
|
|
|
|
for mut entry in self.scheduled_jobs.iter_mut() {
|
|
let job = entry.value_mut();
|
|
|
|
if job.enabled && job.next_run <= now {
|
|
info!("Running scheduled job: {}", job.name);
|
|
|
|
// Update job timing
|
|
job.last_run = Some(now);
|
|
job.next_run = self.calculate_next_run(&job.schedule, now);
|
|
|
|
// Execute job (simplified for this implementation)
|
|
self.execute_job(job).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn execute_job(&self, job: &ScheduledJob) -> RTXEvalResult<()> {
|
|
// This would execute the actual benchmark job
|
|
info!("Executing benchmark job: {}", job.name);
|
|
Ok(())
|
|
}
|
|
|
|
fn calculate_next_run(
|
|
&self,
|
|
schedule: &Schedule,
|
|
current: chrono::DateTime<chrono::Utc>,
|
|
) -> chrono::DateTime<chrono::Utc> {
|
|
match schedule {
|
|
Schedule::Interval(duration) => {
|
|
current
|
|
+ chrono::Duration::from_std(*duration).unwrap_or(chrono::Duration::hours(1))
|
|
}
|
|
Schedule::Cron(_) => current + chrono::Duration::hours(24), // Simplified
|
|
_ => current + chrono::Duration::hours(1),
|
|
}
|
|
}
|
|
|
|
pub async fn get_job_status(&self) -> RTXEvalResult<HashMap<String, JobStatus>> {
|
|
let mut status = HashMap::new();
|
|
|
|
for entry in self.scheduled_jobs.iter() {
|
|
let job = entry.value();
|
|
status.insert(
|
|
job.id.clone(),
|
|
JobStatus {
|
|
name: job.name.clone(),
|
|
last_run: job.last_run,
|
|
next_run: job.next_run,
|
|
success_rate: 0.95, // Mock data
|
|
average_duration: Duration::from_secs(300),
|
|
},
|
|
);
|
|
}
|
|
|
|
Ok(status)
|
|
}
|
|
}
|
|
|
|
impl RegressionDetector {
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
config,
|
|
historical_results: Arc::new(RwLock::new(HashMap::new())),
|
|
})
|
|
}
|
|
|
|
pub async fn add_result(&self, result: HistoricalResult) -> RTXEvalResult<()> {
|
|
let mut results = self.historical_results.write().await;
|
|
let key = format!("{}:{:?}", result.benchmark_name, result.category);
|
|
|
|
results.entry(key).or_insert_with(Vec::new).push(result);
|
|
|
|
// Maintain history limit
|
|
for (_, history) in results.iter_mut() {
|
|
if history.len() > self.config.max_history {
|
|
history.drain(0..history.len() - self.config.max_history);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn check_for_regressions(&self) -> RTXEvalResult<Vec<RegressionAlert>> {
|
|
// Simplified regression detection
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
pub async fn detect_regressions(
|
|
&self,
|
|
_results: &crate::EvaluationReport,
|
|
) -> RTXEvalResult<Vec<RegressionAlert>> {
|
|
// Simplified implementation
|
|
Ok(Vec::new())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RegressionAlert {
|
|
pub benchmark_name: String,
|
|
pub category: BenchmarkCategory,
|
|
pub regression_percentage: f64,
|
|
pub current_value: f64,
|
|
pub expected_value: f64,
|
|
}
|
|
|
|
impl PerformanceTracker {
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
config,
|
|
metrics_storage: Arc::new(RwLock::new(HashMap::new())),
|
|
})
|
|
}
|
|
|
|
pub async fn add_performance_point(
|
|
&self,
|
|
result: &crate::core::BenchmarkResult,
|
|
) -> RTXEvalResult<()> {
|
|
let mut storage = self.metrics_storage.write().await;
|
|
|
|
for (metric_name, value) in &result.metrics {
|
|
let key = format!("{}:{}", result.benchmark_name, metric_name);
|
|
|
|
let point = PerformancePoint {
|
|
timestamp: result.start_time,
|
|
benchmark: result.benchmark_name.clone(),
|
|
metric_name: metric_name.clone(),
|
|
value: *value,
|
|
baseline_comparison: 0.0, // Would calculate against baseline
|
|
};
|
|
|
|
storage.entry(key).or_insert_with(Vec::new).push(point);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_performance_trends(&self) -> RTXEvalResult<HashMap<String, PerformanceTrend>> {
|
|
// Simplified trend analysis
|
|
Ok(HashMap::new())
|
|
}
|
|
}
|
|
|
|
impl AlertingSystem {
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
config,
|
|
alert_history: Arc::new(RwLock::new(Vec::new())),
|
|
})
|
|
}
|
|
|
|
pub async fn send_regression_alert(&self, regression: RegressionAlert) -> RTXEvalResult<()> {
|
|
let alert = Alert {
|
|
id: uuid::Uuid::new_v4().to_string(),
|
|
severity: if regression.regression_percentage > 10.0 {
|
|
AlertSeverity::Critical
|
|
} else {
|
|
AlertSeverity::Warning
|
|
},
|
|
title: format!(
|
|
"Performance Regression Detected: {}",
|
|
regression.benchmark_name
|
|
),
|
|
message: format!(
|
|
"Regression of {:.2}% detected in {} benchmark. Current: {:.4}, Expected: {:.4}",
|
|
regression.regression_percentage,
|
|
regression.benchmark_name,
|
|
regression.current_value,
|
|
regression.expected_value
|
|
),
|
|
timestamp: chrono::Utc::now(),
|
|
benchmark_name: regression.benchmark_name,
|
|
regression_percentage: regression.regression_percentage,
|
|
channels_notified: Vec::new(),
|
|
};
|
|
|
|
self.send_alert(alert).await
|
|
}
|
|
|
|
async fn send_alert(&self, alert: Alert) -> RTXEvalResult<()> {
|
|
info!("ALERT: {} - {}", alert.title, alert.message);
|
|
|
|
let mut history = self.alert_history.write().await;
|
|
history.push(alert);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_recent_alerts(&self) -> RTXEvalResult<Vec<Alert>> {
|
|
let history = self.alert_history.read().await;
|
|
Ok(history.clone())
|
|
}
|
|
}
|
|
|
|
impl CompetitorAnalyzer {
|
|
pub fn new(config: AutomationConfig) -> Result<Self> {
|
|
Ok(Self {
|
|
config,
|
|
competitor_baselines: Arc::new(RwLock::new(HashMap::new())),
|
|
})
|
|
}
|
|
|
|
pub async fn get_latest_comparison(&self) -> RTXEvalResult<Option<CompetitorComparison>> {
|
|
// Simplified implementation
|
|
Ok(Some(CompetitorComparison {
|
|
rtx_advantage: hashmap! {
|
|
"overall_performance".to_owned() => 6.5,
|
|
"memory_efficiency".to_owned() => 35.0,
|
|
"inference_latency".to_owned() => 85.0
|
|
},
|
|
last_updated: chrono::Utc::now(),
|
|
confidence_interval: (5.2, 7.8),
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl Default for AutomationConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
ci_integration: true,
|
|
schedule_interval: Duration::from_secs(3600), // 1 hour
|
|
regression_threshold: 5.0, // 5% threshold
|
|
max_history: 100,
|
|
enable_alerts: true,
|
|
alert_channels: Vec::new(),
|
|
competitor_analysis: true,
|
|
results_directory: PathBuf::from("./benchmark_results"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_automation_creation() {
|
|
let config = AutomationConfig::default();
|
|
let automation = BenchmarkAutomation::new(config);
|
|
assert!(automation.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduler_job_creation() {
|
|
let config = AutomationConfig::default();
|
|
let scheduler = BenchmarkScheduler::new(config).unwrap();
|
|
|
|
let job = ScheduledJob {
|
|
id: "test-job".to_owned(),
|
|
name: "Test Job".to_owned(),
|
|
categories: vec![BenchmarkCategory::Performance],
|
|
schedule: Schedule::Manual,
|
|
next_run: chrono::Utc::now(),
|
|
last_run: None,
|
|
enabled: true,
|
|
};
|
|
|
|
let result = scheduler.schedule_job(job).await;
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_regression_detector() {
|
|
let config = AutomationConfig::default();
|
|
let detector = RegressionDetector::new(config).unwrap();
|
|
|
|
let result = HistoricalResult {
|
|
timestamp: chrono::Utc::now(),
|
|
benchmark_name: "test".to_owned(),
|
|
category: BenchmarkCategory::Performance,
|
|
performance_score: 0.95,
|
|
accuracy: 0.92,
|
|
throughput: 1000.0,
|
|
git_commit: None,
|
|
rtx_version: "1.0.0".to_owned(),
|
|
};
|
|
|
|
let add_result = detector.add_result(result).await;
|
|
assert!(add_result.is_ok());
|
|
}
|
|
}
|