use crate::performance_regression::{RegressionSeverity};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use thiserror::Error;
use std::collections::{BTreeMap};
use std::time::SystemTime;
use std::hash::Hash;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use std::io::{self, Read, Write};
use std::fs;
#[derive(Error, Debug)]
pub enum ThresholdError {
#[error("Failed to load threshold config: {0}")]
LoadError(String),
#[error("Failed to save threshold config: {0}")]
SaveError(String),
#[error("Invalid threshold value: {0}")]
InvalidValue(String),
#[error("Threshold configuration error: {0}")]
ConfigError(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ThresholdLevel {
Development, Staging, Production, Critical, Custom(String), }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ThresholdType {
Absolute, Percentage, Percentile, Adaptive, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdRule {
pub name: String,
pub level: ThresholdLevel,
pub threshold_type: ThresholdType,
pub value: f64,
pub metric_name: String,
pub description: String,
pub enabled: bool,
pub priority: u8, pub conditions: HashMap<String, String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdConfig {
pub rules: Vec<ThresholdRule>,
pub default_level: ThresholdLevel,
pub adaptive_threshold_window: usize, pub auto_calibration: bool, pub calibration_interval_hours: u64, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdHistory {
pub timestamp: u64,
pub rule_name: String,
pub old_value: f64,
pub new_value: f64,
pub reason: String,
pub triggered_by: String, }
#[derive(Debug, Clone)]
pub struct ThresholdStats {
pub total_rules: usize,
pub enabled_rules: usize,
pub rules_by_level: HashMap<String, usize>,
pub rules_by_type: HashMap<String, usize>,
pub auto_calibrated_count: usize,
}
#[derive(Debug, Clone)]
pub struct ThresholdSuggestion {
pub rule_name: String,
pub current_value: f64,
pub suggested_value: f64,
pub confidence: f64, pub reason: String,
pub impact_assessment: String,
}
pub struct ThresholdManager {
config: ThresholdConfig,
history: Vec<ThresholdHistory>,
config_dir: PathBuf,
adaptive_cache: HashMap<String, Vec<f64>>, }
impl ThresholdManager {
pub fn new(config: ThresholdConfig, config_dir: PathBuf) -> Self {
if !config_dir.exists() {
fs::create_dir_all(&config_dir).unwrap_or_else(|e| {
eprintln!("Failed to create threshold config directory: {}", e);
});
}
Self {
config,
history: Vec::new(),
config_dir,
adaptive_cache: HashMap::new(),
}
}
pub fn new_default() -> Self {
let config_dir: _ = PathBuf::from("threshold_configs");
let config: _ = ThresholdConfig::default();
Self::new(config, config_dir)
}
pub fn load_config(&mut self) -> Result<(), ThresholdError> {
let config_path: _ = self.config_dir.join("thresholds.json");
if !config_path.exists() {
self.save_config()?;
return Ok(());
}
let content: _ = fs::read_to_string(&config_path)
.map_err(|e| ThresholdError::LoadError(e.to_string()))?;
self.config = serde_json::from_str(&content)
.map_err(|e| ThresholdError::LoadError(e.to_string()))?;
Ok(())
}
pub fn save_config(&self) -> Result<(), ThresholdError> {
let config_path: _ = self.config_dir.join("thresholds.json");
let content: _ = serde_json::to_string_pretty(&self.config)
.map_err(|e| ThresholdError::SaveError(e.to_string()))?;
fs::write(&config_path, content)
.map_err(|e| ThresholdError::SaveError(e.to_string()))?;
Ok(())
}
pub fn add_rule(&mut self, rule: ThresholdRule) -> Result<(), ThresholdError> {
if rule.value < 0.0 {
return Err(ThresholdError::InvalidValue(
"Threshold value cannot be negative".to_string(),
));
}
if self.config.rules.iter().any(|r| r.name == rule.name) {
return Err(ThresholdError::InvalidValue(
format!("Rule '{}' already exists", rule.name)));
}
self.config.rules.push(rule);
Ok(())
}
pub fn update_rule(
&mut self,
rule_name: &str,
new_value: f64,
reason: &str,
) -> Result<(), ThresholdError> {
let timestamp: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
if let Some(rule) = self.config.rules.iter_mut().find(|r| r.name == rule_name) {
self.history.push(ThresholdHistory {
timestamp,
rule_name: rule_name.to_string(),
old_value: rule.value,
new_value,
reason: reason.to_string(),
triggered_by: "manual_update".to_string(),
});
rule.value = new_value;
Ok(())
} else {
Err(ThresholdError::ConfigError(
format!("Rule '{}' not found", rule_name)))
}
}
pub fn remove_rule(&mut self, rule_name: &str) -> Result<(), ThresholdError> {
if let Some(index) = self.config.rules.iter().position(|r| r.name == rule_name) {
self.config.rules.remove(index);
Ok(())
} else {
Err(ThresholdError::ConfigError(
format!("Rule '{}' not found", rule_name)))
}
}
pub fn get_threshold(
&self,
metric_name: &str,
level: &ThresholdLevel,
) -> Option<&ThresholdRule> {
let mut candidates: Vec<&ThresholdRule> = self.config.rules
.iter()
.filter(|r| r.enabled && r.metric_name == metric_name && &r.level == level)
.collect();
if candidates.is_empty() {
candidates = self.config.rules
.iter()
.filter(|r| r.enabled && r.metric_name == metric_name && &r.level == &self.config.default_level)
.collect();
}
if candidates.is_empty() {
candidates = self.config.rules
.iter()
.filter(|r| r.enabled && r.metric_name == metric_name)
.collect();
}
candidates.into_iter().max_by_key(|r| r.priority)
}
pub fn adjust_threshold_for_severity(
&mut self,
rule_name: &str,
severity: RegressionSeverity,
current_regression_percent: f64,
) -> Result<f64, ThresholdError> {
if let Some(rule) = self.config.rules.iter_mut().find(|r| r.name == rule_name) {
let adjustment_factor: _ = match severity {
RegressionSeverity::None => 0.0,
RegressionSeverity::Minor => 0.05, RegressionSeverity::Moderate => 0.10, RegressionSeverity::Severe => 0.20, RegressionSeverity::Critical => 0.30, };
let adjusted_value: _ = rule.value * (1.0 + adjustment_factor);
let timestamp: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
self.history.push(ThresholdHistory {
timestamp,
rule_name: rule_name.to_string(),
old_value: rule.value,
new_value: adjusted_value,
reason: format!("Auto-adjust for {:?} severity regression", severity),
triggered_by: "auto_adjustment".to_string(),
});
rule.value = adjusted_value;
Ok(adjusted_value)
} else {
Err(ThresholdError::ConfigError(
format!("Rule '{}' not found", rule_name)))
}
}
pub fn add_adaptive_data(&mut self, metric_name: &str, value: f64) {
let cache_key: _ = metric_name.to_string();
let window_size: _ = self.config.adaptive_threshold_window;
let values: _ = self.adaptive_cache.entry(cache_key).or_insert_with(Vec::new);
values.push(value);
if values.len() > window_size {
values.remove(0);
}
}
pub fn generate_suggestions(&self) -> Vec<ThresholdSuggestion> {
let mut suggestions = Vec::new();
for rule in &self.config.rules {
if rule.threshold_type == ThresholdType::Adaptive {
let cache_key: _ = &rule.metric_name;
if let Some(values) = self.adaptive_cache.get(cache_key) {
if values.len() >= 10 { let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
let variance: f64 = values
.iter()
.map(|&v| {
let diff: _ = v - mean;
diff * diff
})
.sum::<f64>()
/ values.len() as f64;
let std_dev: _ = variance.sqrt();
let suggested_value: _ = mean + (2.0 * std_dev); let confidence: _ = (values.len() as f64 / self.config.adaptive_threshold_window as f64)
.min(1.0);
suggestions.push(ThresholdSuggestion {
rule_name: rule.name.clone(),
current_value: rule.value,
suggested_value,
confidence,
reason: "Based on historical data analysis".to_string(),
impact_assessment: if (suggested_value - rule.value).abs() / rule.value > 0.2 {
"High impact - significant threshold change".to_string()
} else {
"Low impact - minor adjustment".to_string()
},
});
}
}
}
}
suggestions
}
pub fn apply_suggestion(
&mut self,
rule_name: &str,
suggested_value: f64,
reason: &str,
) -> Result<(), ThresholdError> {
self.update_rule(rule_name, suggested_value, reason)
}
pub fn get_stats(&self) -> ThresholdStats {
let mut rules_by_level = HashMap::new();
let mut rules_by_type = HashMap::new();
let mut enabled_count = 0;
let mut auto_calibrated_count = 0;
for rule in &self.config.rules {
if rule.enabled {
enabled_count += 1;
*rules_by_level.entry(rule.level.clone()).or_insert(0) += 1;
*rules_by_type.entry(rule.threshold_type.clone()).or_insert(0) += 1;
}
if rule.conditions.get("auto_calibrated") == Some(&"true".to_string()) {
auto_calibrated_count += 1;
}
}
ThresholdStats {
total_rules: self.config.rules.len(),
enabled_rules: enabled_count,
rules_by_level,
rules_by_type,
auto_calibrated_count,
}
}
pub fn get_history(&self) -> &[ThresholdHistory] {
&self.history
}
pub fn cleanup_history(&mut self, max_age_days: u64) {
let cutoff_time: _ = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
- (max_age_days * 24 * 60 * 60);
self.history.retain(|record| record.timestamp >= cutoff_time);
}
pub fn export_config(&self) -> Result<String, ThresholdError> {
serde_json::to_string_pretty(&self.config)
.map_err(|e| ThresholdError::SaveError(e.to_string()))
}
pub fn import_config(&mut self, config_json: &str) -> Result<(), ThresholdError> {
let new_config: ThresholdConfig = serde_json::from_str(config_json)
.map_err(|e| ThresholdError::LoadError(e.to_string()))?;
self.config = new_config;
Ok(())
}
pub fn set_default_level(&mut self, level: ThresholdLevel) {
self.config.default_level = level;
}
pub fn set_rule_enabled(&mut self, rule_name: &str, enabled: bool) -> Result<(), ThresholdError> {
if let Some(rule) = self.config.rules.iter_mut().find(|r| r.name == rule_name) {
rule.enabled = enabled;
Ok(())
} else {
Err(ThresholdError::ConfigError(
format!("Rule '{}' not found", rule_name)))
}
}
}
impl Default for ThresholdConfig {
fn default() -> Self {
let mut rules = Vec::new();
rules.push(ThresholdRule {
name: "startup_time_regression".to_string(),
level: ThresholdLevel::Production,
threshold_type: ThresholdType::Percentage,
value: 10.0,
metric_name: "startup_time".to_string(),
description: "Startup time regression threshold".to_string(),
enabled: true,
priority: 100,
conditions: HashMap::new(),
});
rules.push(ThresholdRule {
name: "execution_time_regression".to_string(),
level: ThresholdLevel::Production,
threshold_type: ThresholdType::Percentage,
value: 5.0,
metric_name: "execution_time".to_string(),
description: "Execution time regression threshold".to_string(),
enabled: true,
priority: 100,
conditions: HashMap::new(),
});
rules.push(ThresholdRule {
name: "memory_regression".to_string(),
level: ThresholdLevel::Production,
threshold_type: ThresholdType::Percentage,
value: 15.0,
metric_name: "memory_usage".to_string(),
description: "Memory usage regression threshold".to_string(),
enabled: true,
priority: 100,
conditions: HashMap::new(),
});
rules.push(ThresholdRule {
name: "throughput_regression".to_string(),
level: ThresholdLevel::Production,
threshold_type: ThresholdType::Percentage,
value: 8.0,
metric_name: "throughput".to_string(),
description: "Throughput regression threshold".to_string(),
enabled: true,
priority: 100,
conditions: HashMap::new(),
});
Self {
rules,
default_level: ThresholdLevel::Production,
adaptive_threshold_window: 100,
auto_calibration: false,
calibration_interval_hours: 24,
}
}
}