use crate::error::{RusTorchError, RusTorchResult};
use crate::profiler::benchmark_suite::{BenchmarkResult, BenchmarkStatistics};
use crate::profiler::core::{PerformanceStatistics, SessionSnapshot};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrendDirection {
Improving,
Degrading,
Stable,
Unknown,
}
#[derive(Debug, Clone)]
pub struct PerformanceTrend {
pub operation_name: String,
pub direction: TrendDirection,
pub change_rate_percent: f64,
pub confidence: f64,
pub measurements: Vec<TrendMeasurement>,
pub projection: Option<PerformanceProjection>,
}
#[derive(Debug, Clone)]
pub struct TrendMeasurement {
pub timestamp: Instant,
pub value: f64,
pub context: MeasurementContext,
}
#[derive(Debug, Clone)]
pub struct MeasurementContext {
pub system_load: Option<f64>,
pub memory_pressure: Option<f64>,
pub gpu_utilization: Option<f64>,
pub temperature_celsius: Option<f32>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct PerformanceProjection {
pub next_value: f64,
pub confidence: f64,
pub time_horizon: Duration,
pub value_range: (f64, f64),
}
#[derive(Debug, Clone)]
pub struct OptimizationRecommendation {
pub id: String,
pub target: String,
pub recommendation_type: RecommendationType,
pub priority: RecommendationPriority,
pub description: String,
pub action: String,
pub expected_improvement: ExpectedImprovement,
pub complexity: ImplementationComplexity,
pub evidence: Vec<String>,
pub timestamp: Instant,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecommendationType {
Algorithm,
Memory,
Gpu,
Caching,
Parallelization,
DataStructure,
SystemConfig,
Hardware,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum RecommendationPriority {
Critical,
High,
Medium,
Low,
Optional,
}
#[derive(Debug, Clone)]
pub struct ExpectedImprovement {
pub performance_gain_percent: f64,
pub memory_reduction_percent: Option<f64>,
pub energy_efficiency_gain_percent: Option<f64>,
pub confidence: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImplementationComplexity {
Trivial,
Simple,
Moderate,
Complex,
Expert,
}
#[derive(Debug, Clone)]
pub struct TrendAnalysisConfig {
pub min_measurements: usize,
pub lookback_window: Duration,
pub significance_threshold: f64,
pub min_recommendation_confidence: f64,
}
impl Default for TrendAnalysisConfig {
fn default() -> Self {
Self {
min_measurements: 5,
lookback_window: Duration::from_secs(3600), significance_threshold: 0.05, min_recommendation_confidence: 0.7,
}
}
}
#[derive(Debug)]
pub struct PerformanceAnalyzer {
config: TrendAnalysisConfig,
performance_history: HashMap<String, Vec<TrendMeasurement>>,
trend_cache: HashMap<String, PerformanceTrend>,
recommendations: Vec<OptimizationRecommendation>,
baselines: HashMap<String, f64>,
last_analysis: Option<Instant>,
}
impl PerformanceAnalyzer {
pub fn new() -> Self {
Self {
config: TrendAnalysisConfig::default(),
performance_history: HashMap::new(),
trend_cache: HashMap::new(),
recommendations: Vec::new(),
baselines: HashMap::new(),
last_analysis: None,
}
}
pub fn with_config(config: TrendAnalysisConfig) -> Self {
Self {
config,
performance_history: HashMap::new(),
trend_cache: HashMap::new(),
recommendations: Vec::new(),
baselines: HashMap::new(),
last_analysis: None,
}
}
pub fn add_measurement(
&mut self,
operation_name: String,
value: f64,
context: MeasurementContext,
) {
let measurement = TrendMeasurement {
timestamp: Instant::now(),
value,
context,
};
self.performance_history
.entry(operation_name.clone())
.or_default()
.push(measurement);
self.trend_cache.remove(&operation_name);
if let Some(history) = self.performance_history.get_mut(&operation_name) {
if history.len() > 1000 {
history.drain(0..500);
}
}
}
pub fn analyze_session(&mut self, session: &SessionSnapshot) -> RusTorchResult<TrendAnalysis> {
println!("🔍 Analyzing session: {}", session.session_name);
let mut trends = HashMap::new();
for operation in &session.operations {
let stats = operation.get_statistics();
let context = MeasurementContext {
system_load: None,
memory_pressure: None,
gpu_utilization: None,
temperature_celsius: None,
metadata: HashMap::new(),
};
self.add_measurement(operation.name.clone(), stats.avg_time_ms, context);
if let Ok(trend) = self.analyze_operation_trend(&operation.name) {
trends.insert(operation.name.clone(), trend);
}
}
self.generate_recommendations(&trends)?;
self.last_analysis = Some(Instant::now());
Ok(TrendAnalysis {
session_id: session.session_id.clone(),
session_name: session.session_name.clone(),
analysis_timestamp: Instant::now(),
operation_trends: trends.clone(),
overall_performance_score: self.calculate_overall_score(&session.operations)?,
recommendations: self.recommendations.clone(),
summary: self.generate_analysis_summary(&trends),
})
}
pub fn analyze_benchmark_results(
&mut self,
results: &HashMap<String, BenchmarkResult>,
) -> RusTorchResult<BenchmarkAnalysis> {
println!(
"📊 Analyzing benchmark results ({} benchmarks)",
results.len()
);
let mut analysis_results = HashMap::new();
let mut performance_insights = Vec::new();
for (name, result) in results {
if result.error.is_none() {
let context = MeasurementContext {
system_load: result
.system_metrics
.as_ref()
.map(|s| s.cpu_utilization_percent),
memory_pressure: result
.memory_metrics
.as_ref()
.map(|m| m.fragmentation_score),
gpu_utilization: result
.gpu_metrics
.as_ref()
.map(|g| g.gpu_utilization_percent),
temperature_celsius: result
.gpu_metrics
.as_ref()
.and_then(|g| g.gpu_temperature_celsius),
metadata: HashMap::new(),
};
self.add_measurement(name.clone(), result.statistics.mean_ms, context);
let analysis = self.analyze_benchmark_performance(result)?;
analysis_results.insert(name.clone(), analysis);
if let Some(insights) = self.generate_benchmark_insights(result) {
performance_insights.extend(insights);
}
}
}
Ok(BenchmarkAnalysis {
analysis_timestamp: Instant::now(),
benchmark_results: analysis_results,
performance_insights,
comparative_analysis: self.generate_comparative_analysis(results)?,
optimization_opportunities: self.identify_optimization_opportunities(results)?,
})
}
pub fn set_baseline(&mut self, operation_name: String, baseline_value: f64) {
self.baselines.insert(operation_name, baseline_value);
}
pub fn get_trend(&mut self, operation_name: &str) -> RusTorchResult<PerformanceTrend> {
if let Some(cached_trend) = self.trend_cache.get(operation_name) {
return Ok(cached_trend.clone());
}
let trend = self.analyze_operation_trend(operation_name)?;
self.trend_cache
.insert(operation_name.to_string(), trend.clone());
Ok(trend)
}
pub fn get_recommendations(&self) -> &[OptimizationRecommendation] {
&self.recommendations
}
pub fn get_recommendations_by_priority(
&self,
priority: RecommendationPriority,
) -> Vec<&OptimizationRecommendation> {
self.recommendations
.iter()
.filter(|r| r.priority == priority)
.collect()
}
pub fn clear(&mut self) {
self.performance_history.clear();
self.trend_cache.clear();
self.recommendations.clear();
self.baselines.clear();
self.last_analysis = None;
}
fn analyze_operation_trend(&self, operation_name: &str) -> RusTorchResult<PerformanceTrend> {
let measurements = self
.performance_history
.get(operation_name)
.ok_or_else(|| RusTorchError::Profiling {
message: format!("No measurements for operation: {}", operation_name),
})?;
if measurements.len() < self.config.min_measurements {
return Ok(PerformanceTrend {
operation_name: operation_name.to_string(),
direction: TrendDirection::Unknown,
change_rate_percent: 0.0,
confidence: 0.0,
measurements: measurements.clone(),
projection: None,
});
}
let (slope, confidence) = self.calculate_linear_trend(measurements)?;
let direction = if slope.abs() < self.config.significance_threshold {
TrendDirection::Stable
} else if slope < 0.0 {
TrendDirection::Improving } else {
TrendDirection::Degrading
};
let change_rate_percent = slope * 100.0;
let projection = if confidence > 0.5 {
Some(self.generate_projection(measurements, slope, confidence)?)
} else {
None
};
Ok(PerformanceTrend {
operation_name: operation_name.to_string(),
direction,
change_rate_percent,
confidence,
measurements: measurements.clone(),
projection,
})
}
fn calculate_linear_trend(
&self,
measurements: &[TrendMeasurement],
) -> RusTorchResult<(f64, f64)> {
if measurements.len() < 2 {
return Ok((0.0, 0.0));
}
let first_time = measurements[0].timestamp;
let x_values: Vec<f64> = measurements
.iter()
.map(|m| m.timestamp.duration_since(first_time).as_secs_f64())
.collect();
let y_values: Vec<f64> = measurements.iter().map(|m| m.value).collect();
let n = measurements.len() as f64;
let sum_x: f64 = x_values.iter().sum();
let sum_y: f64 = y_values.iter().sum();
let sum_xy: f64 = x_values.iter().zip(&y_values).map(|(x, y)| x * y).sum();
let sum_x2: f64 = x_values.iter().map(|x| x * x).sum();
let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
let mean_y = sum_y / n;
let ss_tot: f64 = y_values.iter().map(|y| (y - mean_y).powi(2)).sum();
let intercept = (sum_y - slope * sum_x) / n;
let ss_res: f64 = x_values
.iter()
.zip(&y_values)
.map(|(x, y)| (y - (slope * x + intercept)).powi(2))
.sum();
let r_squared = if ss_tot > 0.0 {
1.0 - (ss_res / ss_tot)
} else {
0.0
};
let confidence = r_squared.max(0.0).min(1.0);
Ok((slope, confidence))
}
fn generate_projection(
&self,
measurements: &[TrendMeasurement],
slope: f64,
confidence: f64,
) -> RusTorchResult<PerformanceProjection> {
let last_measurement = &measurements[measurements.len() - 1];
let time_horizon = Duration::from_secs(3600);
let next_value = last_measurement.value + slope * time_horizon.as_secs_f64();
let uncertainty_factor = (1.0 - confidence) * 0.2; let range_width = next_value * uncertainty_factor;
Ok(PerformanceProjection {
next_value,
confidence,
time_horizon,
value_range: (next_value - range_width, next_value + range_width),
})
}
fn generate_recommendations(
&mut self,
trends: &HashMap<String, PerformanceTrend>,
) -> RusTorchResult<()> {
self.recommendations.clear();
for (operation_name, trend) in trends {
match trend.direction {
TrendDirection::Degrading
if trend.confidence > self.config.min_recommendation_confidence =>
{
let recommendation =
self.create_degradation_recommendation(operation_name, trend)?;
self.recommendations.push(recommendation);
}
TrendDirection::Stable => {
if let Some(recommendation) =
self.create_optimization_recommendation(operation_name, trend)?
{
self.recommendations.push(recommendation);
}
}
_ => {}
}
}
self.recommendations
.sort_by(|a, b| a.priority.cmp(&b.priority));
Ok(())
}
fn create_degradation_recommendation(
&self,
operation_name: &str,
trend: &PerformanceTrend,
) -> RusTorchResult<OptimizationRecommendation> {
let priority = if trend.change_rate_percent > 20.0 {
RecommendationPriority::Critical
} else if trend.change_rate_percent > 10.0 {
RecommendationPriority::High
} else {
RecommendationPriority::Medium
};
Ok(OptimizationRecommendation {
id: format!("degradation_{}", operation_name),
target: operation_name.to_string(),
recommendation_type: RecommendationType::Algorithm,
priority,
description: format!(
"Performance degradation detected: {:.1}% slower over recent measurements",
trend.change_rate_percent
),
action: "Investigate recent changes and profile operation for bottlenecks".to_string(),
expected_improvement: ExpectedImprovement {
performance_gain_percent: trend.change_rate_percent,
memory_reduction_percent: None,
energy_efficiency_gain_percent: None,
confidence: trend.confidence,
},
complexity: ImplementationComplexity::Moderate,
evidence: vec![
format!("Trend confidence: {:.1}%", trend.confidence * 100.0),
format!("Change rate: {:.1}%", trend.change_rate_percent),
],
timestamp: Instant::now(),
})
}
fn create_optimization_recommendation(
&self,
_operation_name: &str,
_trend: &PerformanceTrend,
) -> RusTorchResult<Option<OptimizationRecommendation>> {
Ok(None)
}
fn analyze_benchmark_performance(
&self,
_result: &BenchmarkResult,
) -> RusTorchResult<BenchmarkPerformanceAnalysis> {
Ok(BenchmarkPerformanceAnalysis {
stability_score: 0.9,
efficiency_score: 0.8,
comparison_to_baseline: None,
bottleneck_indicators: Vec::new(),
})
}
fn generate_benchmark_insights(
&self,
_result: &BenchmarkResult,
) -> Option<Vec<PerformanceInsight>> {
None
}
fn generate_comparative_analysis(
&self,
_results: &HashMap<String, BenchmarkResult>,
) -> RusTorchResult<ComparativeAnalysis> {
Ok(ComparativeAnalysis {
performance_ranking: Vec::new(),
relative_performance: HashMap::new(),
category_analysis: HashMap::new(),
})
}
fn identify_optimization_opportunities(
&self,
_results: &HashMap<String, BenchmarkResult>,
) -> RusTorchResult<Vec<OptimizationOpportunity>> {
Ok(Vec::new())
}
fn calculate_overall_score(
&self,
_operations: &[crate::profiler::core::OperationMetrics],
) -> RusTorchResult<f64> {
Ok(0.85) }
fn generate_analysis_summary(
&self,
trends: &HashMap<String, PerformanceTrend>,
) -> AnalysisSummary {
let total_operations = trends.len();
let improving_count = trends
.values()
.filter(|t| t.direction == TrendDirection::Improving)
.count();
let degrading_count = trends
.values()
.filter(|t| t.direction == TrendDirection::Degrading)
.count();
let stable_count = trends
.values()
.filter(|t| t.direction == TrendDirection::Stable)
.count();
AnalysisSummary {
total_operations,
improving_count,
degrading_count,
stable_count,
critical_recommendations: self
.get_recommendations_by_priority(RecommendationPriority::Critical)
.len(),
high_recommendations: self
.get_recommendations_by_priority(RecommendationPriority::High)
.len(),
}
}
}
#[derive(Debug, Clone)]
pub struct TrendAnalysis {
pub session_id: String,
pub session_name: String,
pub analysis_timestamp: Instant,
pub operation_trends: HashMap<String, PerformanceTrend>,
pub overall_performance_score: f64,
pub recommendations: Vec<OptimizationRecommendation>,
pub summary: AnalysisSummary,
}
#[derive(Debug, Clone)]
pub struct AnalysisSummary {
pub total_operations: usize,
pub improving_count: usize,
pub degrading_count: usize,
pub stable_count: usize,
pub critical_recommendations: usize,
pub high_recommendations: usize,
}
#[derive(Debug, Clone)]
pub struct BenchmarkAnalysis {
pub analysis_timestamp: Instant,
pub benchmark_results: HashMap<String, BenchmarkPerformanceAnalysis>,
pub performance_insights: Vec<PerformanceInsight>,
pub comparative_analysis: ComparativeAnalysis,
pub optimization_opportunities: Vec<OptimizationOpportunity>,
}
#[derive(Debug, Clone)]
pub struct BenchmarkPerformanceAnalysis {
pub stability_score: f64,
pub efficiency_score: f64,
pub comparison_to_baseline: Option<f64>,
pub bottleneck_indicators: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct PerformanceInsight {
pub category: String,
pub description: String,
pub impact_level: RecommendationPriority,
}
#[derive(Debug, Clone)]
pub struct ComparativeAnalysis {
pub performance_ranking: Vec<String>,
pub relative_performance: HashMap<String, f64>,
pub category_analysis: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct OptimizationOpportunity {
pub operation: String,
pub opportunity_type: RecommendationType,
pub potential_gain: f64,
pub implementation_effort: ImplementationComplexity,
}
impl Default for PerformanceAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyzer_creation() {
let analyzer = PerformanceAnalyzer::new();
assert_eq!(analyzer.recommendations.len(), 0);
assert_eq!(analyzer.performance_history.len(), 0);
}
#[test]
fn test_measurement_addition() {
let mut analyzer = PerformanceAnalyzer::new();
let context = MeasurementContext {
system_load: Some(0.5),
memory_pressure: None,
gpu_utilization: None,
temperature_celsius: None,
metadata: HashMap::new(),
};
analyzer.add_measurement("test_op".to_string(), 100.0, context);
assert_eq!(analyzer.performance_history.len(), 1);
assert!(analyzer.performance_history.contains_key("test_op"));
assert_eq!(analyzer.performance_history["test_op"].len(), 1);
}
#[test]
fn test_trend_analysis_insufficient_data() {
let analyzer = PerformanceAnalyzer::new();
let result = analyzer.analyze_operation_trend("nonexistent_op");
assert!(result.is_err());
}
#[test]
fn test_baseline_setting() {
let mut analyzer = PerformanceAnalyzer::new();
analyzer.set_baseline("test_op".to_string(), 50.0);
assert!(analyzer.baselines.contains_key("test_op"));
assert_eq!(analyzer.baselines["test_op"], 50.0);
}
#[test]
fn test_linear_trend_calculation() {
let analyzer = PerformanceAnalyzer::new();
let start_time = Instant::now();
let measurements = vec![
TrendMeasurement {
timestamp: start_time,
value: 100.0,
context: MeasurementContext {
system_load: None,
memory_pressure: None,
gpu_utilization: None,
temperature_celsius: None,
metadata: HashMap::new(),
},
},
TrendMeasurement {
timestamp: start_time + Duration::from_secs(60),
value: 110.0,
context: MeasurementContext {
system_load: None,
memory_pressure: None,
gpu_utilization: None,
temperature_celsius: None,
metadata: HashMap::new(),
},
},
];
let result = analyzer.calculate_linear_trend(&measurements);
assert!(result.is_ok());
let (slope, confidence) = result.unwrap();
assert!(slope > 0.0); assert!(confidence >= 0.0 && confidence <= 1.0);
}
}