Skip to main content

optirs_learned/transformer_based_optimizer/
performance_tracker.rs

1// Performance tracking for transformer-based optimizer
2
3use super::config::PerformanceConfig;
4use super::meta_learning::MetaLearningResult;
5use super::TrainingMetrics;
6use crate::error::Result;
7use scirs2_core::ndarray::Array1;
8use scirs2_core::numeric::Float;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, VecDeque};
11use std::fmt::Debug;
12use std::time::{Duration, Instant};
13
14/// Performance metrics for transformer optimizer
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct PerformanceMetrics {
17    /// Training metrics
18    pub training_metrics: TrainingMetricsCollection,
19
20    /// Inference metrics
21    pub inference_metrics: InferenceMetricsCollection,
22
23    /// Memory usage metrics
24    pub memory_metrics: MemoryMetricsCollection,
25
26    /// Computation time metrics
27    pub timing_metrics: TimingMetricsCollection,
28
29    /// Model quality metrics
30    pub quality_metrics: QualityMetricsCollection,
31
32    /// Resource utilization metrics
33    pub resource_metrics: ResourceMetricsCollection,
34
35    /// Optimization metrics
36    pub optimization_metrics: OptimizationMetricsCollection,
37}
38
39/// Transformer performance tracker
40pub struct TransformerPerformanceTracker<
41    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
42> {
43    /// Configuration
44    config: PerformanceConfig,
45
46    /// Performance metrics
47    metrics: PerformanceMetrics,
48
49    /// Loss history
50    loss_history: VecDeque<f64>,
51
52    /// Training step timings
53    step_timings: VecDeque<Duration>,
54
55    /// Meta-learning results history
56    meta_results_history: VecDeque<MetaLearningResult<T>>,
57
58    /// Training metrics history
59    training_history: VecDeque<TrainingMetrics>,
60
61    /// Memory usage samples
62    memory_samples: VecDeque<MemorySample>,
63
64    /// Performance baselines
65    baselines: PerformanceBaselines,
66
67    /// Alert thresholds
68    alert_thresholds: AlertThresholds,
69
70    /// Performance trends
71    trends: PerformanceTrends,
72
73    /// Profiling data
74    profiling_data: ProfilingData,
75
76    /// Start time for tracking session
77    session_start: Instant,
78}
79
80impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
81    for TransformerPerformanceTracker<T>
82{
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
89    TransformerPerformanceTracker<T>
90{
91    /// Create new performance tracker
92    pub fn new() -> Self {
93        let config = PerformanceConfig::default();
94
95        Self {
96            config,
97            metrics: PerformanceMetrics::new(),
98            loss_history: VecDeque::new(),
99            step_timings: VecDeque::new(),
100            meta_results_history: VecDeque::new(),
101            training_history: VecDeque::new(),
102            memory_samples: VecDeque::new(),
103            baselines: PerformanceBaselines::new(),
104            alert_thresholds: AlertThresholds::default(),
105            trends: PerformanceTrends::new(),
106            profiling_data: ProfilingData::new(),
107            session_start: Instant::now(),
108        }
109    }
110
111    /// Create with custom configuration
112    pub fn with_config(config: PerformanceConfig) -> Self {
113        let mut tracker = Self::new();
114        tracker.config = config;
115        tracker
116    }
117
118    /// Record optimization step performance
119    pub fn record_optimization_step(&mut self, duration: Duration, update: &Array1<T>) {
120        // Record timing
121        self.step_timings.push_back(duration);
122        if self.step_timings.len() > self.config.max_history_size {
123            self.step_timings.pop_front();
124        }
125
126        // Update timing metrics
127        self.metrics.timing_metrics.record_step_time(duration);
128
129        // Analyze update quality
130        let update_norm = self.compute_array_norm(update);
131        self.metrics
132            .optimization_metrics
133            .record_update_norm(update_norm);
134
135        // Update trends
136        self.trends.update_step_timing(duration);
137
138        // Check for performance alerts
139        self.check_performance_alerts(duration, update_norm);
140    }
141
142    /// Record training epoch performance
143    pub fn record_training_epoch(&mut self, metrics: TrainingMetrics) {
144        self.training_history.push_back(metrics.clone());
145        if self.training_history.len() > self.config.max_history_size {
146            self.training_history.pop_front();
147        }
148
149        // Update training metrics
150        self.metrics.training_metrics.record_epoch(
151            metrics.loss,
152            metrics.training_time,
153            metrics.convergence_rate,
154        );
155
156        // Update trends
157        self.trends.update_training_loss(metrics.loss);
158        self.trends
159            .update_convergence_rate(metrics.convergence_rate);
160
161        // Check for convergence alerts
162        self.check_convergence_alerts(&metrics);
163    }
164
165    /// Record meta-learning step performance
166    pub fn record_meta_step(&mut self, result: MetaLearningResult<T>) {
167        self.meta_results_history.push_back(result.clone());
168        if self.meta_results_history.len() > 100 {
169            self.meta_results_history.pop_front();
170        }
171
172        // Update meta-learning metrics
173        self.metrics.training_metrics.record_meta_step(
174            result.meta_loss,
175            result.computation_time,
176            result.task_adaptations.len(),
177        );
178
179        // Update trends
180        self.trends.update_meta_loss(result.meta_loss);
181        self.trends
182            .update_adaptation_efficiency(result.task_adaptations.len() as f64);
183    }
184
185    /// Record inference performance
186    pub fn record_inference(&mut self, input_size: usize, duration: Duration, output_quality: f64) {
187        self.metrics
188            .inference_metrics
189            .record_inference(input_size, duration, output_quality);
190
191        // Update throughput calculations
192        let throughput = input_size as f64 / duration.as_secs_f64();
193        self.metrics.inference_metrics.update_throughput(throughput);
194
195        // Update trends
196        self.trends.update_inference_latency(duration);
197        self.trends.update_inference_quality(output_quality);
198    }
199
200    /// Record memory usage
201    pub fn record_memory_usage(&mut self, usage: MemoryUsage) {
202        let sample = MemorySample {
203            timestamp: std::time::SystemTime::now(),
204            usage,
205        };
206
207        self.memory_samples.push_back(sample.clone());
208        if self.memory_samples.len() > self.config.max_history_size {
209            self.memory_samples.pop_front();
210        }
211
212        // Update memory metrics
213        self.metrics.memory_metrics.record_usage(usage);
214
215        // Feed the resource-utilization history too. Without this
216        // `average_memory_utilization()` stayed `None` forever even though the
217        // caller *was* supplying real samples — the same write-only-history bug
218        // the CPU metric had.
219        self.metrics
220            .resource_metrics
221            .record_memory_usage(usage.total_memory as f64);
222
223        // Update trends
224        self.trends.update_memory_usage(usage.total_memory);
225
226        // Check for memory alerts
227        self.check_memory_alerts(&usage);
228    }
229
230    /// Record loss value
231    pub fn record_loss(&mut self, loss: f64) {
232        self.loss_history.push_back(loss);
233        if self.loss_history.len() > self.config.max_history_size {
234            self.loss_history.pop_front();
235        }
236
237        // Update quality metrics
238        self.metrics.quality_metrics.record_loss(loss);
239
240        // Update trends
241        self.trends.update_loss(loss);
242    }
243
244    /// Profile operation performance
245    pub fn profile_operation<F, R>(&mut self, operation_name: &str, operation: F) -> Result<R>
246    where
247        F: FnOnce() -> Result<R>,
248    {
249        let start_time = Instant::now();
250        let result = operation();
251        let duration = start_time.elapsed();
252
253        // Record profiling data
254        self.profiling_data
255            .record_operation(operation_name.to_string(), duration);
256
257        // Update timing metrics
258        self.metrics
259            .timing_metrics
260            .record_operation_time(operation_name, duration);
261
262        result
263    }
264
265    /// Generate performance report
266    pub fn generate_report(&self) -> PerformanceReport {
267        let session_duration = self.session_start.elapsed();
268
269        PerformanceReport {
270            session_duration,
271            total_optimization_steps: self.step_timings.len(),
272            total_training_epochs: self.training_history.len(),
273            total_meta_steps: self.meta_results_history.len(),
274
275            // Average performance metrics
276            average_step_time: self.calculate_average_step_time(),
277            average_loss: self.calculate_average_loss(),
278            current_convergence_rate: self.calculate_current_convergence_rate(),
279
280            // Memory statistics
281            peak_memory_usage: self.calculate_peak_memory_usage(),
282            average_memory_usage: self.calculate_average_memory_usage(),
283
284            // Quality metrics
285            best_loss: self.calculate_best_loss(),
286            loss_improvement: self.calculate_loss_improvement(),
287
288            // Performance trends
289            loss_trend: self.trends.get_loss_trend(),
290            convergence_trend: self.trends.get_convergence_trend(),
291            memory_trend: self.trends.get_memory_trend(),
292
293            // Alert summary
294            performance_alerts: self.get_recent_alerts(),
295
296            // Resource utilization. `None` means "never sampled", which is a
297            // different claim from "measured zero".
298            cpu_utilization: self.metrics.resource_metrics.average_cpu_utilization(),
299            memory_utilization: self.metrics.resource_metrics.average_memory_utilization(),
300
301            // Model quality assessment
302            quality_score: self.calculate_overall_quality_score(),
303
304            // Recommendations
305            recommendations: self.generate_recommendations(),
306        }
307    }
308
309    /// Get loss history
310    pub fn get_loss_history(&self) -> &VecDeque<f64> {
311        &self.loss_history
312    }
313
314    /// Get current performance metrics
315    pub fn get_metrics(&self) -> &PerformanceMetrics {
316        &self.metrics
317    }
318
319    /// Get performance trends
320    pub fn get_trends(&self) -> &PerformanceTrends {
321        &self.trends
322    }
323
324    /// Set performance baselines
325    pub fn set_baselines(&mut self, baselines: PerformanceBaselines) {
326        self.baselines = baselines;
327    }
328
329    /// Reset all performance tracking
330    pub fn reset(&mut self) {
331        self.metrics = PerformanceMetrics::new();
332        self.loss_history.clear();
333        self.step_timings.clear();
334        self.meta_results_history.clear();
335        self.training_history.clear();
336        self.memory_samples.clear();
337        self.trends.reset();
338        self.profiling_data.reset();
339        self.session_start = Instant::now();
340    }
341
342    /// Helper methods
343    fn compute_array_norm(&self, array: &Array1<T>) -> f64 {
344        let sum_squares: T = array
345            .iter()
346            .map(|&x| x * x)
347            .fold(T::zero(), |acc, x| acc + x);
348        sum_squares.sqrt().to_f64().unwrap_or(0.0)
349    }
350
351    fn calculate_average_step_time(&self) -> Duration {
352        if self.step_timings.is_empty() {
353            Duration::new(0, 0)
354        } else {
355            let total: Duration = self.step_timings.iter().sum();
356            total / self.step_timings.len() as u32
357        }
358    }
359
360    fn calculate_average_loss(&self) -> f64 {
361        if self.loss_history.is_empty() {
362            0.0
363        } else {
364            self.loss_history.iter().sum::<f64>() / self.loss_history.len() as f64
365        }
366    }
367
368    fn calculate_current_convergence_rate(&self) -> f64 {
369        if let Some(recent_training) = self.training_history.back() {
370            recent_training.convergence_rate
371        } else {
372            0.0
373        }
374    }
375
376    fn calculate_peak_memory_usage(&self) -> usize {
377        self.memory_samples
378            .iter()
379            .map(|sample| sample.usage.total_memory)
380            .max()
381            .unwrap_or(0)
382    }
383
384    fn calculate_average_memory_usage(&self) -> f64 {
385        if self.memory_samples.is_empty() {
386            0.0
387        } else {
388            let total: usize = self
389                .memory_samples
390                .iter()
391                .map(|s| s.usage.total_memory)
392                .sum();
393            total as f64 / self.memory_samples.len() as f64
394        }
395    }
396
397    fn calculate_best_loss(&self) -> f64 {
398        self.loss_history
399            .iter()
400            .fold(f64::INFINITY, |min, &loss| min.min(loss))
401    }
402
403    fn calculate_loss_improvement(&self) -> f64 {
404        if self.loss_history.len() < 2 {
405            return 0.0;
406        }
407
408        // `len() >= 2` was checked above, so both ends exist; reading through
409        // the fallible accessors keeps that reasoning local.
410        let (Some(&initial_loss), Some(&final_loss)) =
411            (self.loss_history.front(), self.loss_history.back())
412        else {
413            return 0.0;
414        };
415
416        if initial_loss > 0.0 {
417            (initial_loss - final_loss) / initial_loss
418        } else {
419            0.0
420        }
421    }
422
423    fn calculate_overall_quality_score(&self) -> f64 {
424        // Weighted combination of various quality metrics
425        let loss_score = 1.0 - (self.calculate_average_loss() / 10.0).min(1.0);
426        let convergence_score = self.calculate_current_convergence_rate();
427        let stability_score = 1.0 - self.trends.get_loss_volatility().min(1.0);
428
429        (loss_score * 0.4 + convergence_score * 0.3 + stability_score * 0.3).clamp(0.0, 1.0)
430    }
431
432    fn generate_recommendations(&self) -> Vec<String> {
433        let mut recommendations = Vec::new();
434
435        // Loss-based recommendations
436        if self.trends.get_loss_trend() > 0.0 {
437            recommendations
438                .push("Loss is increasing. Consider reducing learning rate.".to_string());
439        }
440
441        // Memory-based recommendations
442        if self.calculate_peak_memory_usage() > 1024 * 1024 * 1024 {
443            // 1GB
444            recommendations.push(
445                "High memory usage detected. Consider enabling memory compression.".to_string(),
446            );
447        }
448
449        // Performance-based recommendations
450        if self.calculate_average_step_time() > Duration::from_millis(100) {
451            recommendations.push(
452                "Slow optimization steps. Consider reducing model size or batch size.".to_string(),
453            );
454        }
455
456        // Convergence-based recommendations
457        if self.calculate_current_convergence_rate() < 0.1 {
458            recommendations.push(
459                "Low convergence rate. Consider adjusting meta-learning parameters.".to_string(),
460            );
461        }
462
463        recommendations
464    }
465
466    fn check_performance_alerts(&mut self, duration: Duration, update_norm: f64) {
467        // Check for slow steps
468        if duration > self.alert_thresholds.max_step_time {
469            self.record_alert(AlertType::SlowStep, format!("Step took {:?}", duration));
470        }
471
472        // Check for gradient explosion
473        if update_norm > self.alert_thresholds.max_gradient_norm {
474            self.record_alert(
475                AlertType::GradientExplosion,
476                format!("Update norm: {:.6}", update_norm),
477            );
478        }
479
480        // Check for gradient vanishing
481        if update_norm < self.alert_thresholds.min_gradient_norm {
482            self.record_alert(
483                AlertType::GradientVanishing,
484                format!("Update norm: {:.6}", update_norm),
485            );
486        }
487    }
488
489    fn check_convergence_alerts(&mut self, metrics: &TrainingMetrics) {
490        // Check for training stagnation
491        if metrics.convergence_rate < self.alert_thresholds.min_convergence_rate {
492            self.record_alert(
493                AlertType::TrainingStagnation,
494                format!("Convergence rate: {:.6}", metrics.convergence_rate),
495            );
496        }
497
498        // Check for loss increase
499        if let Some(previous) = self.training_history.iter().rev().nth(1) {
500            if metrics.loss > previous.loss * 1.1 {
501                // 10% increase
502                self.record_alert(
503                    AlertType::LossIncrease,
504                    format!(
505                        "Loss increased from {:.6} to {:.6}",
506                        previous.loss, metrics.loss
507                    ),
508                );
509            }
510        }
511    }
512
513    fn check_memory_alerts(&mut self, usage: &MemoryUsage) {
514        // Check for high memory usage
515        if usage.total_memory > self.alert_thresholds.max_memory_usage {
516            self.record_alert(
517                AlertType::HighMemoryUsage,
518                format!("Memory usage: {} bytes", usage.total_memory),
519            );
520        }
521
522        // Check for memory leaks
523        if let Some(previous) = self.memory_samples.back() {
524            let memory_increase = usage
525                .total_memory
526                .saturating_sub(previous.usage.total_memory);
527            if memory_increase > 1024 * 1024 * 100 {
528                // 100MB increase
529                self.record_alert(
530                    AlertType::PossibleMemoryLeak,
531                    format!("Memory increased by {} bytes", memory_increase),
532                );
533            }
534        }
535    }
536
537    fn record_alert(&mut self, alert_type: AlertType, message: String) {
538        let alert = PerformanceAlert {
539            alert_type,
540            message,
541            timestamp: std::time::SystemTime::now(),
542        };
543
544        self.metrics.quality_metrics.record_alert(alert);
545    }
546
547    fn get_recent_alerts(&self) -> Vec<PerformanceAlert> {
548        self.metrics.quality_metrics.get_recent_alerts(10)
549    }
550}
551
552/// Performance metrics collections
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct TrainingMetricsCollection {
555    pub total_epochs: usize,
556    pub total_training_time: Duration,
557    pub average_loss: f64,
558    pub best_loss: f64,
559    pub convergence_rate: f64,
560    pub meta_steps: usize,
561    pub task_adaptations: usize,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct InferenceMetricsCollection {
566    pub total_inferences: usize,
567    pub average_latency: Duration,
568    pub peak_throughput: f64,
569    pub average_quality: f64,
570    pub cache_hit_rate: f64,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct MemoryMetricsCollection {
575    pub peak_usage: usize,
576    pub average_usage: f64,
577    pub total_allocations: usize,
578    pub compression_ratio: f64,
579    pub fragmentation_rate: f64,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
583pub struct TimingMetricsCollection {
584    pub average_step_time: Duration,
585    pub total_computation_time: Duration,
586    pub operation_timings: HashMap<String, Duration>,
587    pub profiling_overhead: Duration,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct QualityMetricsCollection {
592    pub loss_statistics: LossStatistics,
593    pub convergence_statistics: ConvergenceStatistics,
594    pub stability_metrics: StabilityMetrics,
595    pub performance_alerts: VecDeque<PerformanceAlert>,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct ResourceMetricsCollection {
600    pub cpu_usage_history: VecDeque<f64>,
601    pub memory_usage_history: VecDeque<f64>,
602    pub disk_io_metrics: DiskIOMetrics,
603    pub network_metrics: NetworkMetrics,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607pub struct OptimizationMetricsCollection {
608    pub update_norm_history: VecDeque<f64>,
609    pub parameter_change_rate: f64,
610    pub optimization_efficiency: f64,
611    pub adaptive_learning_metrics: AdaptiveLearningMetrics,
612}
613
614/// Supporting data structures
615#[derive(Debug, Clone)]
616pub struct MemorySample {
617    pub timestamp: std::time::SystemTime,
618    pub usage: MemoryUsage,
619}
620
621#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
622pub struct MemoryUsage {
623    pub total_memory: usize,
624    pub model_memory: usize,
625    pub cache_memory: usize,
626    pub temporary_memory: usize,
627}
628
629#[derive(Debug, Clone)]
630pub struct PerformanceBaselines {
631    pub baseline_loss: f64,
632    pub baseline_step_time: Duration,
633    pub baseline_memory_usage: usize,
634    pub baseline_convergence_rate: f64,
635}
636
637impl Default for PerformanceBaselines {
638    fn default() -> Self {
639        Self::new()
640    }
641}
642
643impl PerformanceBaselines {
644    pub fn new() -> Self {
645        Self {
646            baseline_loss: f64::INFINITY,
647            baseline_step_time: Duration::new(0, 0),
648            baseline_memory_usage: 0,
649            baseline_convergence_rate: 0.0,
650        }
651    }
652}
653
654#[derive(Debug, Clone)]
655pub struct AlertThresholds {
656    pub max_step_time: Duration,
657    pub min_convergence_rate: f64,
658    pub max_memory_usage: usize,
659    pub max_gradient_norm: f64,
660    pub min_gradient_norm: f64,
661}
662
663impl Default for AlertThresholds {
664    fn default() -> Self {
665        Self {
666            max_step_time: Duration::from_secs(1),
667            min_convergence_rate: 0.01,
668            max_memory_usage: 2 * 1024 * 1024 * 1024, // 2GB
669            max_gradient_norm: 10.0,
670            min_gradient_norm: 1e-8,
671        }
672    }
673}
674
675#[derive(Debug, Clone)]
676pub struct PerformanceTrends {
677    loss_trend: TrendAnalyzer,
678    convergence_trend: TrendAnalyzer,
679    memory_trend: TrendAnalyzer,
680    timing_trend: TrendAnalyzer,
681}
682
683impl Default for PerformanceTrends {
684    fn default() -> Self {
685        Self::new()
686    }
687}
688
689impl PerformanceTrends {
690    pub fn new() -> Self {
691        Self {
692            loss_trend: TrendAnalyzer::new(100),
693            convergence_trend: TrendAnalyzer::new(50),
694            memory_trend: TrendAnalyzer::new(100),
695            timing_trend: TrendAnalyzer::new(100),
696        }
697    }
698
699    pub fn update_loss(&mut self, loss: f64) {
700        self.loss_trend.add_sample(loss);
701    }
702
703    pub fn update_training_loss(&mut self, loss: f64) {
704        self.loss_trend.add_sample(loss);
705    }
706
707    pub fn update_meta_loss(&mut self, loss: f64) {
708        self.loss_trend.add_sample(loss);
709    }
710
711    pub fn update_convergence_rate(&mut self, rate: f64) {
712        self.convergence_trend.add_sample(rate);
713    }
714
715    pub fn update_adaptation_efficiency(&mut self, efficiency: f64) {
716        self.convergence_trend.add_sample(efficiency);
717    }
718
719    pub fn update_memory_usage(&mut self, usage: usize) {
720        self.memory_trend.add_sample(usage as f64);
721    }
722
723    pub fn update_step_timing(&mut self, duration: Duration) {
724        self.timing_trend.add_sample(duration.as_secs_f64());
725    }
726
727    pub fn update_inference_latency(&mut self, duration: Duration) {
728        self.timing_trend.add_sample(duration.as_secs_f64());
729    }
730
731    pub fn update_inference_quality(&mut self, _quality: f64) {
732        // Could be used for quality trend analysis
733    }
734
735    pub fn get_loss_trend(&self) -> f64 {
736        self.loss_trend.get_trend()
737    }
738
739    pub fn get_convergence_trend(&self) -> f64 {
740        self.convergence_trend.get_trend()
741    }
742
743    pub fn get_memory_trend(&self) -> f64 {
744        self.memory_trend.get_trend()
745    }
746
747    pub fn get_loss_volatility(&self) -> f64 {
748        self.loss_trend.get_volatility()
749    }
750
751    pub fn reset(&mut self) {
752        self.loss_trend.reset();
753        self.convergence_trend.reset();
754        self.memory_trend.reset();
755        self.timing_trend.reset();
756    }
757}
758
759#[derive(Debug, Clone)]
760pub struct TrendAnalyzer {
761    samples: VecDeque<f64>,
762    max_samples: usize,
763}
764
765impl TrendAnalyzer {
766    pub fn new(max_samples: usize) -> Self {
767        Self {
768            samples: VecDeque::new(),
769            max_samples,
770        }
771    }
772
773    pub fn add_sample(&mut self, value: f64) {
774        self.samples.push_back(value);
775        if self.samples.len() > self.max_samples {
776            self.samples.pop_front();
777        }
778    }
779
780    pub fn get_trend(&self) -> f64 {
781        if self.samples.len() < 2 {
782            return 0.0;
783        }
784
785        // Simple linear trend calculation
786        let n = self.samples.len() as f64;
787        let x_sum = (0..self.samples.len()).map(|i| i as f64).sum::<f64>();
788        let y_sum = self.samples.iter().sum::<f64>();
789        let xy_sum = self
790            .samples
791            .iter()
792            .enumerate()
793            .map(|(i, &y)| i as f64 * y)
794            .sum::<f64>();
795        let x_sq_sum = (0..self.samples.len())
796            .map(|i| (i as f64).powi(2))
797            .sum::<f64>();
798
799        let denominator = n * x_sq_sum - x_sum * x_sum;
800        if denominator.abs() < 1e-10 {
801            0.0
802        } else {
803            (n * xy_sum - x_sum * y_sum) / denominator
804        }
805    }
806
807    pub fn get_volatility(&self) -> f64 {
808        if self.samples.len() < 2 {
809            return 0.0;
810        }
811
812        let mean = self.samples.iter().sum::<f64>() / self.samples.len() as f64;
813        let variance = self
814            .samples
815            .iter()
816            .map(|&x| (x - mean).powi(2))
817            .sum::<f64>()
818            / self.samples.len() as f64;
819
820        variance.sqrt()
821    }
822
823    pub fn reset(&mut self) {
824        self.samples.clear();
825    }
826}
827
828#[derive(Debug, Clone)]
829pub struct ProfilingData {
830    operation_timings: HashMap<String, VecDeque<Duration>>,
831    total_operations: usize,
832}
833
834impl Default for ProfilingData {
835    fn default() -> Self {
836        Self::new()
837    }
838}
839
840impl ProfilingData {
841    pub fn new() -> Self {
842        Self {
843            operation_timings: HashMap::new(),
844            total_operations: 0,
845        }
846    }
847
848    pub fn record_operation(&mut self, operation: String, duration: Duration) {
849        let timings = self.operation_timings.entry(operation).or_default();
850        timings.push_back(duration);
851        if timings.len() > 1000 {
852            timings.pop_front();
853        }
854        self.total_operations += 1;
855    }
856
857    pub fn get_average_time(&self, operation: &str) -> Option<Duration> {
858        self.operation_timings.get(operation).map(|timings| {
859            let total: Duration = timings.iter().sum();
860            total / timings.len() as u32
861        })
862    }
863
864    pub fn reset(&mut self) {
865        self.operation_timings.clear();
866        self.total_operations = 0;
867    }
868}
869
870/// Alert system
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct PerformanceAlert {
873    pub alert_type: AlertType,
874    pub message: String,
875    pub timestamp: std::time::SystemTime,
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize)]
879pub enum AlertType {
880    SlowStep,
881    GradientExplosion,
882    GradientVanishing,
883    TrainingStagnation,
884    LossIncrease,
885    HighMemoryUsage,
886    PossibleMemoryLeak,
887    ConvergenceFailure,
888}
889
890/// Performance report
891#[derive(Debug, Clone)]
892pub struct PerformanceReport {
893    pub session_duration: Duration,
894    pub total_optimization_steps: usize,
895    pub total_training_epochs: usize,
896    pub total_meta_steps: usize,
897    pub average_step_time: Duration,
898    pub average_loss: f64,
899    pub current_convergence_rate: f64,
900    pub peak_memory_usage: usize,
901    pub average_memory_usage: f64,
902    pub best_loss: f64,
903    pub loss_improvement: f64,
904    pub loss_trend: f64,
905    pub convergence_trend: f64,
906    pub memory_trend: f64,
907    pub performance_alerts: Vec<PerformanceAlert>,
908    /// Mean observed CPU utilization fraction, or `None` when no sample was ever
909    /// recorded. This was an `f64` that reported `0.0` unconditionally because
910    /// nothing ever wrote to the CPU history — a fabricated measurement. Feed it
911    /// with `ResourceMetricsCollection::record_cpu_utilization`.
912    pub cpu_utilization: Option<f64>,
913    /// Mean observed memory usage in bytes, or `None` when no sample was ever
914    /// recorded.
915    pub memory_utilization: Option<f64>,
916    pub quality_score: f64,
917    pub recommendations: Vec<String>,
918}
919
920/// Additional metric structures (simplified implementations)
921#[derive(Debug, Clone, Serialize, Deserialize)]
922pub struct LossStatistics {
923    pub mean: f64,
924    pub std_dev: f64,
925    pub min: f64,
926    pub max: f64,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
930pub struct ConvergenceStatistics {
931    pub average_rate: f64,
932    pub best_rate: f64,
933    pub convergence_episodes: usize,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct StabilityMetrics {
938    pub loss_volatility: f64,
939    pub gradient_stability: f64,
940    pub convergence_stability: f64,
941}
942
943#[derive(Debug, Clone, Serialize, Deserialize)]
944pub struct DiskIOMetrics {
945    pub total_reads: usize,
946    pub total_writes: usize,
947    pub total_bytes_read: usize,
948    pub total_bytes_written: usize,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952pub struct NetworkMetrics {
953    pub total_requests: usize,
954    pub total_bytes_sent: usize,
955    pub total_bytes_received: usize,
956    pub average_latency: Duration,
957}
958
959#[derive(Debug, Clone, Serialize, Deserialize)]
960pub struct AdaptiveLearningMetrics {
961    pub learning_rate_adjustments: usize,
962    pub batch_size_adjustments: usize,
963    pub architecture_modifications: usize,
964}
965
966/// Implementation of basic functionality for metric collections
967impl Default for PerformanceMetrics {
968    fn default() -> Self {
969        Self::new()
970    }
971}
972
973impl PerformanceMetrics {
974    pub fn new() -> Self {
975        Self {
976            training_metrics: TrainingMetricsCollection::new(),
977            inference_metrics: InferenceMetricsCollection::new(),
978            memory_metrics: MemoryMetricsCollection::new(),
979            timing_metrics: TimingMetricsCollection::new(),
980            quality_metrics: QualityMetricsCollection::new(),
981            resource_metrics: ResourceMetricsCollection::new(),
982            optimization_metrics: OptimizationMetricsCollection::new(),
983        }
984    }
985}
986
987impl Default for TrainingMetricsCollection {
988    fn default() -> Self {
989        Self::new()
990    }
991}
992
993impl TrainingMetricsCollection {
994    pub fn new() -> Self {
995        Self {
996            total_epochs: 0,
997            total_training_time: Duration::new(0, 0),
998            average_loss: 0.0,
999            best_loss: f64::INFINITY,
1000            convergence_rate: 0.0,
1001            meta_steps: 0,
1002            task_adaptations: 0,
1003        }
1004    }
1005
1006    pub fn record_epoch(&mut self, loss: f64, duration: Duration, convergence: f64) {
1007        self.total_epochs += 1;
1008        self.total_training_time += duration;
1009        self.average_loss =
1010            (self.average_loss * (self.total_epochs - 1) as f64 + loss) / self.total_epochs as f64;
1011        self.best_loss = self.best_loss.min(loss);
1012        self.convergence_rate = convergence;
1013    }
1014
1015    pub fn record_meta_step(&mut self, loss: f64, _duration: Duration, adaptations: usize) {
1016        self.meta_steps += 1;
1017        self.task_adaptations += adaptations;
1018        self.best_loss = self.best_loss.min(loss);
1019    }
1020}
1021
1022impl Default for InferenceMetricsCollection {
1023    fn default() -> Self {
1024        Self::new()
1025    }
1026}
1027
1028impl InferenceMetricsCollection {
1029    pub fn new() -> Self {
1030        Self {
1031            total_inferences: 0,
1032            average_latency: Duration::new(0, 0),
1033            peak_throughput: 0.0,
1034            average_quality: 0.0,
1035            cache_hit_rate: 0.0,
1036        }
1037    }
1038
1039    pub fn record_inference(&mut self, _input_size: usize, duration: Duration, quality: f64) {
1040        self.total_inferences += 1;
1041        self.average_latency = (self.average_latency * (self.total_inferences - 1) as u32
1042            + duration)
1043            / self.total_inferences as u32;
1044        self.average_quality = (self.average_quality * (self.total_inferences - 1) as f64
1045            + quality)
1046            / self.total_inferences as f64;
1047    }
1048
1049    pub fn update_throughput(&mut self, throughput: f64) {
1050        self.peak_throughput = self.peak_throughput.max(throughput);
1051    }
1052}
1053
1054impl Default for MemoryMetricsCollection {
1055    fn default() -> Self {
1056        Self::new()
1057    }
1058}
1059
1060impl MemoryMetricsCollection {
1061    pub fn new() -> Self {
1062        Self {
1063            peak_usage: 0,
1064            average_usage: 0.0,
1065            total_allocations: 0,
1066            compression_ratio: 1.0,
1067            fragmentation_rate: 0.0,
1068        }
1069    }
1070
1071    pub fn record_usage(&mut self, usage: MemoryUsage) {
1072        self.peak_usage = self.peak_usage.max(usage.total_memory);
1073        self.total_allocations += 1;
1074        self.average_usage = (self.average_usage * (self.total_allocations - 1) as f64
1075            + usage.total_memory as f64)
1076            / self.total_allocations as f64;
1077    }
1078}
1079
1080impl Default for TimingMetricsCollection {
1081    fn default() -> Self {
1082        Self::new()
1083    }
1084}
1085
1086impl TimingMetricsCollection {
1087    pub fn new() -> Self {
1088        Self {
1089            average_step_time: Duration::new(0, 0),
1090            total_computation_time: Duration::new(0, 0),
1091            operation_timings: HashMap::new(),
1092            profiling_overhead: Duration::new(0, 0),
1093        }
1094    }
1095
1096    pub fn record_step_time(&mut self, duration: Duration) {
1097        self.total_computation_time += duration;
1098        // Update average step time logic would go here
1099    }
1100
1101    pub fn record_operation_time(&mut self, operation: &str, duration: Duration) {
1102        self.operation_timings
1103            .insert(operation.to_string(), duration);
1104    }
1105}
1106
1107impl Default for QualityMetricsCollection {
1108    fn default() -> Self {
1109        Self::new()
1110    }
1111}
1112
1113impl QualityMetricsCollection {
1114    pub fn new() -> Self {
1115        Self {
1116            loss_statistics: LossStatistics {
1117                mean: 0.0,
1118                std_dev: 0.0,
1119                min: f64::INFINITY,
1120                max: f64::NEG_INFINITY,
1121            },
1122            convergence_statistics: ConvergenceStatistics {
1123                average_rate: 0.0,
1124                best_rate: 0.0,
1125                convergence_episodes: 0,
1126            },
1127            stability_metrics: StabilityMetrics {
1128                loss_volatility: 0.0,
1129                gradient_stability: 0.0,
1130                convergence_stability: 0.0,
1131            },
1132            performance_alerts: VecDeque::new(),
1133        }
1134    }
1135
1136    pub fn record_loss(&mut self, loss: f64) {
1137        self.loss_statistics.min = self.loss_statistics.min.min(loss);
1138        self.loss_statistics.max = self.loss_statistics.max.max(loss);
1139    }
1140
1141    pub fn record_alert(&mut self, alert: PerformanceAlert) {
1142        self.performance_alerts.push_back(alert);
1143        if self.performance_alerts.len() > 100 {
1144            self.performance_alerts.pop_front();
1145        }
1146    }
1147
1148    pub fn get_recent_alerts(&self, count: usize) -> Vec<PerformanceAlert> {
1149        self.performance_alerts
1150            .iter()
1151            .rev()
1152            .take(count)
1153            .cloned()
1154            .collect()
1155    }
1156}
1157
1158impl Default for ResourceMetricsCollection {
1159    fn default() -> Self {
1160        Self::new()
1161    }
1162}
1163
1164impl ResourceMetricsCollection {
1165    pub fn new() -> Self {
1166        Self {
1167            cpu_usage_history: VecDeque::new(),
1168            memory_usage_history: VecDeque::new(),
1169            disk_io_metrics: DiskIOMetrics {
1170                total_reads: 0,
1171                total_writes: 0,
1172                total_bytes_read: 0,
1173                total_bytes_written: 0,
1174            },
1175            network_metrics: NetworkMetrics {
1176                total_requests: 0,
1177                total_bytes_sent: 0,
1178                total_bytes_received: 0,
1179                average_latency: Duration::new(0, 0),
1180            },
1181        }
1182    }
1183
1184    /// Record an observed CPU utilization fraction in `[0, 1]`.
1185    ///
1186    /// Nothing inside this crate can measure process CPU time without an FFI
1187    /// dependency, so the *caller* supplies the sample. Before this existed
1188    /// `cpu_usage_history` had no writers at all, which made
1189    /// `get_average_cpu_usage` return `0.0` forever and let the
1190    /// performance report present that as a measurement of zero CPU use.
1191    ///
1192    /// Out-of-range and non-finite samples are ignored rather than recorded, so a
1193    /// bad caller cannot corrupt the average.
1194    pub fn record_cpu_utilization(&mut self, fraction: f64) {
1195        if !fraction.is_finite() || !(0.0..=1.0).contains(&fraction) {
1196            return;
1197        }
1198        self.cpu_usage_history.push_back(fraction);
1199        while self.cpu_usage_history.len() > 1000 {
1200            self.cpu_usage_history.pop_front();
1201        }
1202    }
1203
1204    /// Mean recorded CPU utilization, or `None` when nothing has been recorded.
1205    ///
1206    /// `None` means "not measured" and is what the report should show; it is not
1207    /// the same statement as "0% CPU", which is what the old `f64`-returning
1208    /// version claimed.
1209    pub fn average_cpu_utilization(&self) -> Option<f64> {
1210        if self.cpu_usage_history.is_empty() {
1211            None
1212        } else {
1213            Some(self.cpu_usage_history.iter().sum::<f64>() / self.cpu_usage_history.len() as f64)
1214        }
1215    }
1216
1217    /// Number of CPU samples recorded.
1218    pub fn cpu_sample_count(&self) -> usize {
1219        self.cpu_usage_history.len()
1220    }
1221
1222    /// Record an observed memory usage in bytes. Called by
1223    /// `TransformerPerformanceTracker::record_memory_usage`.
1224    pub fn record_memory_usage(&mut self, bytes: f64) {
1225        if !bytes.is_finite() || bytes < 0.0 {
1226            return;
1227        }
1228        self.memory_usage_history.push_back(bytes);
1229        while self.memory_usage_history.len() > 1000 {
1230            self.memory_usage_history.pop_front();
1231        }
1232    }
1233
1234    /// Number of memory samples recorded.
1235    pub fn memory_sample_count(&self) -> usize {
1236        self.memory_usage_history.len()
1237    }
1238
1239    /// Mean recorded memory usage in bytes, or `None` when nothing has been
1240    /// recorded. Fed by `TransformerPerformanceTracker::record_memory_usage`.
1241    pub fn average_memory_utilization(&self) -> Option<f64> {
1242        if self.memory_usage_history.is_empty() {
1243            None
1244        } else {
1245            Some(
1246                self.memory_usage_history.iter().sum::<f64>()
1247                    / self.memory_usage_history.len() as f64,
1248            )
1249        }
1250    }
1251}
1252
1253impl Default for OptimizationMetricsCollection {
1254    fn default() -> Self {
1255        Self::new()
1256    }
1257}
1258
1259impl OptimizationMetricsCollection {
1260    pub fn new() -> Self {
1261        Self {
1262            update_norm_history: VecDeque::new(),
1263            parameter_change_rate: 0.0,
1264            optimization_efficiency: 0.0,
1265            adaptive_learning_metrics: AdaptiveLearningMetrics {
1266                learning_rate_adjustments: 0,
1267                batch_size_adjustments: 0,
1268                architecture_modifications: 0,
1269            },
1270        }
1271    }
1272
1273    pub fn record_update_norm(&mut self, norm: f64) {
1274        self.update_norm_history.push_back(norm);
1275        if self.update_norm_history.len() > 1000 {
1276            self.update_norm_history.pop_front();
1277        }
1278    }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use super::*;
1284
1285    #[test]
1286    fn test_performance_tracker_creation() {
1287        let tracker = TransformerPerformanceTracker::<f32>::new();
1288        assert_eq!(tracker.loss_history.len(), 0);
1289        assert_eq!(tracker.step_timings.len(), 0);
1290    }
1291
1292    #[test]
1293    fn test_record_loss() {
1294        let mut tracker = TransformerPerformanceTracker::<f32>::new();
1295        tracker.record_loss(1.5);
1296        tracker.record_loss(1.2);
1297        tracker.record_loss(0.9);
1298
1299        assert_eq!(tracker.loss_history.len(), 3);
1300        assert_eq!(tracker.calculate_best_loss(), 0.9);
1301    }
1302
1303    #[test]
1304    fn test_trend_analyzer() {
1305        let mut analyzer = TrendAnalyzer::new(10);
1306
1307        for i in 0..5 {
1308            analyzer.add_sample(i as f64);
1309        }
1310
1311        let trend = analyzer.get_trend();
1312        assert!(trend > 0.0); // Should be positive for increasing sequence
1313    }
1314
1315    #[test]
1316    fn test_performance_report_generation() {
1317        let mut tracker = TransformerPerformanceTracker::<f32>::new();
1318        tracker.record_loss(2.0);
1319        tracker.record_loss(1.5);
1320        tracker.record_loss(1.0);
1321
1322        let report = tracker.generate_report();
1323        assert!(report.loss_improvement > 0.0);
1324        assert_eq!(report.best_loss, 1.0);
1325    }
1326
1327    #[test]
1328    fn test_profiling() {
1329        let mut tracker = TransformerPerformanceTracker::<f32>::new();
1330
1331        let result = tracker.profile_operation("test_op", || {
1332            std::thread::sleep(Duration::from_millis(10));
1333            Ok(42)
1334        });
1335
1336        assert!(result.is_ok());
1337        assert_eq!(result.expect("profile_operation should succeed"), 42);
1338    }
1339
1340    /// F84: `cpu_usage_history` had no writers anywhere in the crate, so
1341    /// `get_average_cpu_usage()` returned `0.0` forever and the report presented
1342    /// that as a measured 0% CPU. An unsampled metric must now report `None`.
1343    #[test]
1344    fn cpu_utilization_is_none_until_it_is_actually_sampled() {
1345        let mut metrics = ResourceMetricsCollection::new();
1346        assert_eq!(metrics.cpu_sample_count(), 0);
1347        assert_eq!(
1348            metrics.average_cpu_utilization(),
1349            None,
1350            "an unsampled CPU metric must not claim a value"
1351        );
1352
1353        metrics.record_cpu_utilization(0.25);
1354        metrics.record_cpu_utilization(0.75);
1355        assert_eq!(metrics.cpu_sample_count(), 2);
1356        let mean = metrics
1357            .average_cpu_utilization()
1358            .expect("two samples were recorded");
1359        assert!((mean - 0.5).abs() < 1e-12, "mean {mean}");
1360
1361        // Out-of-range and non-finite samples are ignored, not recorded.
1362        metrics.record_cpu_utilization(-0.1);
1363        metrics.record_cpu_utilization(1.5);
1364        metrics.record_cpu_utilization(f64::NAN);
1365        assert_eq!(
1366            metrics.cpu_sample_count(),
1367            2,
1368            "invalid samples must not enter the history"
1369        );
1370    }
1371
1372    /// The report must carry the same "not measured" distinction.
1373    #[test]
1374    fn a_report_reports_unmeasured_resources_as_none() {
1375        let mut tracker = TransformerPerformanceTracker::<f32>::new();
1376        tracker.record_loss(1.0);
1377        let report = tracker.generate_report();
1378        assert_eq!(
1379            report.cpu_utilization, None,
1380            "nothing sampled the CPU, so the report must say so"
1381        );
1382        assert_eq!(report.memory_utilization, None);
1383    }
1384
1385    /// `memory_usage_history` also had no writers, so a caller that *was*
1386    /// supplying real memory samples still got `None` back.
1387    #[test]
1388    fn recorded_memory_usage_reaches_the_report() {
1389        let mut tracker = TransformerPerformanceTracker::<f32>::new();
1390        tracker.record_loss(1.0);
1391        let sample = |total: usize| MemoryUsage {
1392            total_memory: total,
1393            model_memory: total / 2,
1394            cache_memory: total / 4,
1395            temporary_memory: total / 4,
1396        };
1397        tracker.record_memory_usage(sample(4096));
1398        tracker.record_memory_usage(sample(8192));
1399
1400        let report = tracker.generate_report();
1401        let mean = report
1402            .memory_utilization
1403            .expect("two memory samples were recorded");
1404        assert!((mean - 6144.0).abs() < 1e-9, "mean {mean}");
1405        assert_eq!(tracker.metrics.resource_metrics.memory_sample_count(), 2);
1406    }
1407}