Skip to main content

sklears_simd/
performance_monitor.rs

1//! Performance monitoring and tracking utilities
2//!
3//! Provides automated performance tracking, historical analysis, and continuous integration support
4//! for SIMD performance optimization.
5
6use crate::benchmark_framework::{BenchmarkResult, BenchmarkSuite};
7
8#[cfg(not(feature = "no-std"))]
9use std::{
10    collections::{HashMap, HashSet},
11    fs::{File, OpenOptions},
12    io::{BufRead, BufReader, Write},
13    path::Path,
14    string::ToString,
15    time::{SystemTime, UNIX_EPOCH},
16};
17
18#[cfg(feature = "no-std")]
19use alloc::collections::{BTreeMap as HashMap, BTreeSet as HashSet};
20#[cfg(feature = "no-std")]
21use alloc::format;
22#[cfg(feature = "no-std")]
23use alloc::string::{String, ToString};
24#[cfg(feature = "no-std")]
25use alloc::vec::Vec;
26
27// (no-std does not need these aliases; all usages are guarded by #[cfg(not(feature = "no-std"))])
28
29// Helper functions for no-std compatibility
30#[cfg(feature = "no-std")]
31fn current_timestamp() -> u64 {
32    // Mock timestamp for no-std (could use a counter or external time source)
33    0
34}
35
36// Error type for performance monitoring operations
37#[derive(Debug)]
38pub enum PerformanceError {
39    #[cfg(not(feature = "no-std"))]
40    IoError(std::io::Error),
41    Message(String),
42}
43
44#[cfg(not(feature = "no-std"))]
45impl From<std::io::Error> for PerformanceError {
46    fn from(error: std::io::Error) -> Self {
47        PerformanceError::IoError(error)
48    }
49}
50
51/// Performance monitor for tracking results over time
52pub struct PerformanceMonitor {
53    #[allow(dead_code)] // Used by record_results/load_history in std builds; dead in no-std
54    results_file: String,
55    historical_data: Vec<PerformanceRecord>,
56    thresholds: PerformanceThresholds,
57}
58
59/// Historical performance record
60#[derive(Debug, Clone)]
61pub struct PerformanceRecord {
62    pub timestamp: u64,
63    pub git_commit: Option<String>,
64    pub operation: String,
65    pub duration_ns: u64,
66    pub throughput: Option<f64>,
67    pub architecture: String,
68    pub simd_width: usize,
69    pub optimization_level: String,
70}
71
72/// Performance thresholds for alerting
73#[derive(Debug, Clone)]
74pub struct PerformanceThresholds {
75    pub regression_threshold: f64,  // percentage
76    pub improvement_threshold: f64, // percentage
77    pub critical_slowdown: f64,     // percentage
78}
79
80impl Default for PerformanceThresholds {
81    fn default() -> Self {
82        Self {
83            regression_threshold: 5.0,   // 5% regression threshold
84            improvement_threshold: 10.0, // 10% improvement threshold
85            critical_slowdown: 25.0,     // 25% critical slowdown
86        }
87    }
88}
89
90/// Performance alert
91#[derive(Debug)]
92pub struct PerformanceAlert {
93    pub alert_type: AlertType,
94    pub operation: String,
95    pub current_performance: f64,
96    pub baseline_performance: f64,
97    pub change_percent: f64,
98    pub severity: AlertSeverity,
99    pub recommendation: String,
100}
101
102#[derive(Debug)]
103pub enum AlertType {
104    Regression,
105    Improvement,
106    CriticalSlowdown,
107    Anomaly,
108}
109
110#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
111pub enum AlertSeverity {
112    Info,
113    Warning,
114    Error,
115    Critical,
116}
117
118impl PerformanceMonitor {
119    /// Create a new performance monitor
120    #[cfg(not(feature = "no-std"))]
121    pub fn new(results_file: &str) -> std::io::Result<Self> {
122        let mut monitor = Self {
123            results_file: results_file.to_string(),
124            historical_data: Vec::new(),
125            thresholds: PerformanceThresholds::default(),
126        };
127
128        monitor.load_historical_data()?;
129        Ok(monitor)
130    }
131
132    /// Create a new performance monitor (no-std version)
133    #[cfg(feature = "no-std")]
134    pub fn new(results_file: &str) -> Result<Self, &'static str> {
135        let mut monitor = Self {
136            results_file: results_file.to_string(),
137            historical_data: Vec::new(),
138            thresholds: PerformanceThresholds::default(),
139        };
140
141        monitor.load_historical_data()?;
142        Ok(monitor)
143    }
144
145    /// Set custom performance thresholds
146    pub fn set_thresholds(&mut self, thresholds: PerformanceThresholds) {
147        self.thresholds = thresholds;
148    }
149
150    /// Record new performance results
151    #[cfg(not(feature = "no-std"))]
152    pub fn record_results(
153        &mut self,
154        results: &[BenchmarkResult],
155        git_commit: Option<String>,
156    ) -> Result<(), PerformanceError> {
157        #[cfg(not(feature = "no-std"))]
158        let timestamp = SystemTime::now()
159            .duration_since(UNIX_EPOCH)
160            .expect("operation should succeed")
161            .as_secs();
162        #[cfg(feature = "no-std")]
163        let timestamp = current_timestamp();
164
165        let mut file = OpenOptions::new()
166            .create(true)
167            .append(true)
168            .open(&self.results_file)?;
169
170        for result in results {
171            let record = PerformanceRecord {
172                timestamp,
173                git_commit: git_commit.clone(),
174                operation: result.name.clone(),
175                duration_ns: result.duration.as_nanos() as u64,
176                throughput: result.throughput,
177                architecture: result.architecture.clone(),
178                simd_width: result.simd_width,
179                optimization_level: "release".to_string(), // Could be parameterized
180            };
181
182            // Write to file
183            writeln!(
184                file,
185                "{},{},{},{},{},{},{},{},",
186                record.timestamp,
187                record.git_commit.as_ref().unwrap_or(&"unknown".to_string()),
188                record.operation,
189                record.duration_ns,
190                record.throughput.map(|t| t.to_string()).unwrap_or_default(),
191                record.architecture,
192                record.simd_width,
193                record.optimization_level // Reserved for future use
194            )?;
195
196            self.historical_data.push(record);
197        }
198
199        Ok(())
200    }
201
202    /// Record new performance results (no-std version - in-memory only)
203    #[cfg(feature = "no-std")]
204    pub fn record_results(
205        &mut self,
206        results: &[BenchmarkResult],
207        git_commit: Option<String>,
208    ) -> Result<(), &'static str> {
209        let timestamp = 0; // Mock timestamp for no-std
210
211        for result in results {
212            let record = PerformanceRecord {
213                timestamp,
214                git_commit: git_commit.clone(),
215                operation: result.name.clone(),
216                duration_ns: result.duration.as_nanos() as u64,
217                throughput: result.throughput,
218                architecture: result.architecture.clone(),
219                simd_width: result.simd_width,
220                optimization_level: "release".to_string(), // Default optimization level
221            };
222            self.historical_data.push(record);
223        }
224
225        Ok(())
226    }
227
228    /// Analyze performance trends
229    #[cfg(not(feature = "no-std"))]
230    pub fn analyze_trends(&self, operation: &str, days_back: u64) -> PerformanceTrend {
231        #[cfg(not(feature = "no-std"))]
232        let cutoff_timestamp = {
233            let now = SystemTime::now()
234                .duration_since(UNIX_EPOCH)
235                .expect("operation should succeed")
236                .as_secs();
237            let window = days_back.saturating_mul(24 * 60 * 60);
238            now.saturating_sub(window)
239        };
240        #[cfg(feature = "no-std")]
241        let cutoff_timestamp = current_timestamp() - (days_back * 24 * 60 * 60);
242
243        let relevant_data: Vec<&PerformanceRecord> = self
244            .historical_data
245            .iter()
246            .filter(|record| record.operation == operation && record.timestamp >= cutoff_timestamp)
247            .collect();
248
249        if relevant_data.is_empty() {
250            return PerformanceTrend::NoData;
251        }
252
253        // Calculate trend
254        let durations: Vec<f64> = relevant_data.iter().map(|r| r.duration_ns as f64).collect();
255        let trend_slope = self.calculate_trend_slope(&durations);
256
257        let avg_duration = durations.iter().sum::<f64>() / durations.len() as f64;
258        let min_duration = durations.iter().fold(f64::INFINITY, |a, &b| a.min(b));
259        let max_duration = durations.iter().fold(0.0f64, |a, &b| a.max(b));
260
261        PerformanceTrend::Data {
262            operation: operation.to_string(),
263            data_points: relevant_data.len(),
264            trend_slope,
265            avg_duration_ns: avg_duration as u64,
266            min_duration_ns: min_duration as u64,
267            max_duration_ns: max_duration as u64,
268            variance: self.calculate_variance(&durations, avg_duration),
269        }
270    }
271
272    /// Analyze performance trends (no-std version - basic analysis only)
273    #[cfg(feature = "no-std")]
274    pub fn analyze_trends(&self, operation: &str, _days_back: u64) -> PerformanceTrend {
275        let relevant_data: Vec<&PerformanceRecord> = self
276            .historical_data
277            .iter()
278            .filter(|r| r.operation == operation)
279            .collect();
280
281        if relevant_data.is_empty() {
282            return PerformanceTrend::NoData;
283        }
284
285        // Calculate trend
286        let durations: Vec<f64> = relevant_data.iter().map(|r| r.duration_ns as f64).collect();
287        let trend_slope = self.calculate_trend_slope(&durations);
288
289        let avg_duration = durations.iter().sum::<f64>() / durations.len() as f64;
290        let min_duration = durations.iter().fold(f64::INFINITY, |a, &b| a.min(b));
291        let max_duration = durations.iter().fold(0.0f64, |a, &b| a.max(b));
292
293        PerformanceTrend::Data {
294            operation: operation.to_string(),
295            data_points: relevant_data.len(),
296            trend_slope,
297            avg_duration_ns: avg_duration as u64,
298            min_duration_ns: min_duration as u64,
299            max_duration_ns: max_duration as u64,
300            variance: self.calculate_variance(&durations, avg_duration),
301        }
302    }
303
304    /// Check for performance alerts
305    pub fn check_alerts(&self, current_results: &[BenchmarkResult]) -> Vec<PerformanceAlert> {
306        let mut alerts = Vec::new();
307
308        for result in current_results {
309            if let Some(baseline) = self.get_baseline_performance(&result.name) {
310                let current_ns = result.duration.as_nanos() as f64;
311                let baseline_ns = baseline.duration_ns as f64;
312                let change_percent = ((current_ns - baseline_ns) / baseline_ns) * 100.0;
313
314                if change_percent > self.thresholds.critical_slowdown {
315                    alerts.push(PerformanceAlert {
316                        alert_type: AlertType::CriticalSlowdown,
317                        operation: result.name.clone(),
318                        current_performance: current_ns,
319                        baseline_performance: baseline_ns,
320                        change_percent,
321                        severity: AlertSeverity::Critical,
322                        recommendation:
323                            "Critical performance regression detected. Investigate immediately."
324                                .to_string(),
325                    });
326                } else if change_percent > self.thresholds.regression_threshold {
327                    alerts.push(PerformanceAlert {
328                        alert_type: AlertType::Regression,
329                        operation: result.name.clone(),
330                        current_performance: current_ns,
331                        baseline_performance: baseline_ns,
332                        change_percent,
333                        severity: if change_percent > 15.0 {
334                            AlertSeverity::Error
335                        } else {
336                            AlertSeverity::Warning
337                        },
338                        recommendation: format!(
339                            "Performance regression of {:.1}%. Review recent changes.",
340                            change_percent
341                        ),
342                    });
343                } else if change_percent < -self.thresholds.improvement_threshold {
344                    alerts.push(PerformanceAlert {
345                        alert_type: AlertType::Improvement,
346                        operation: result.name.clone(),
347                        current_performance: current_ns,
348                        baseline_performance: baseline_ns,
349                        change_percent,
350                        severity: AlertSeverity::Info,
351                        recommendation: format!(
352                            "Performance improvement of {:.1}%. Great work!",
353                            -change_percent
354                        ),
355                    });
356                }
357            }
358        }
359
360        alerts
361    }
362
363    /// Generate performance report
364    pub fn generate_performance_report(&self, days_back: u64) -> PerformanceReport {
365        let operations: HashSet<String> = self
366            .historical_data
367            .iter()
368            .map(|r| r.operation.clone())
369            .collect();
370
371        let mut trends = HashMap::new();
372        for operation in operations {
373            trends.insert(
374                operation.clone(),
375                self.analyze_trends(&operation, days_back),
376            );
377        }
378
379        #[cfg(not(feature = "no-std"))]
380        let cutoff_timestamp = SystemTime::now()
381            .duration_since(UNIX_EPOCH)
382            .expect("operation should succeed")
383            .as_secs();
384        #[cfg(feature = "no-std")]
385        let cutoff_timestamp = current_timestamp() - (days_back * 24 * 60 * 60);
386
387        let recent_records: Vec<&PerformanceRecord> = self
388            .historical_data
389            .iter()
390            .filter(|record| record.timestamp >= cutoff_timestamp)
391            .collect();
392
393        PerformanceReport {
394            period_days: days_back,
395            total_benchmarks: recent_records.len(),
396            unique_operations: trends.len(),
397            trends,
398            summary: self.generate_summary(&recent_records),
399        }
400    }
401
402    /// Load historical data from file
403    #[cfg(not(feature = "no-std"))]
404    fn load_historical_data(&mut self) -> std::io::Result<()> {
405        if !Path::new(&self.results_file).exists() {
406            return Ok(());
407        }
408
409        let file = File::open(&self.results_file)?;
410        let reader = BufReader::new(file);
411
412        for line in reader.lines() {
413            let line = line?;
414            if let Some(record) = self.parse_record_line(&line) {
415                self.historical_data.push(record);
416            }
417        }
418
419        // Sort by timestamp
420        self.historical_data.sort_by_key(|r| r.timestamp);
421        Ok(())
422    }
423
424    /// Load historical data from file (no-std version - no-op)
425    #[cfg(feature = "no-std")]
426    fn load_historical_data(&mut self) -> Result<(), &'static str> {
427        // No file I/O in no-std mode
428        Ok(())
429    }
430
431    #[cfg(not(feature = "no-std"))]
432    fn parse_record_line(&self, line: &str) -> Option<PerformanceRecord> {
433        let parts: Vec<&str> = line.split(',').collect();
434        if parts.len() < 8 {
435            return None;
436        }
437
438        Some(PerformanceRecord {
439            timestamp: parts[0].parse().ok()?,
440            git_commit: if parts[1] == "unknown" {
441                None
442            } else {
443                Some(parts[1].to_string())
444            },
445            operation: parts[2].to_string(),
446            duration_ns: parts[3].parse().ok()?,
447            throughput: if parts[4].is_empty() {
448                None
449            } else {
450                parts[4].parse().ok()
451            },
452            architecture: parts[5].to_string(),
453            simd_width: parts[6].parse().ok()?,
454            optimization_level: parts[7].to_string(),
455        })
456    }
457
458    fn get_baseline_performance(&self, operation: &str) -> Option<&PerformanceRecord> {
459        // Use the median of the last 10 results as baseline
460        let mut relevant: Vec<&PerformanceRecord> = self
461            .historical_data
462            .iter()
463            .filter(|r| r.operation == operation)
464            .collect();
465
466        if relevant.len() < 5 {
467            return None;
468        }
469
470        relevant.sort_by_key(|r| r.timestamp);
471        let recent = &relevant[relevant.len().saturating_sub(10)..];
472
473        if recent.is_empty() {
474            return None;
475        }
476
477        // Return median performance
478        let mut durations: Vec<&PerformanceRecord> = recent.to_vec();
479        durations.sort_by_key(|r| r.duration_ns);
480        Some(durations[durations.len() / 2])
481    }
482
483    fn calculate_trend_slope(&self, values: &[f64]) -> f64 {
484        if values.len() < 2 {
485            return 0.0;
486        }
487
488        let n = values.len() as f64;
489        let x_mean = (n - 1.0) / 2.0;
490        let y_mean = values.iter().sum::<f64>() / n;
491
492        let numerator: f64 = values
493            .iter()
494            .enumerate()
495            .map(|(i, &y)| (i as f64 - x_mean) * (y - y_mean))
496            .sum();
497
498        let denominator: f64 = (0..values.len()).map(|i| (i as f64 - x_mean).powi(2)).sum();
499
500        if denominator == 0.0 {
501            0.0
502        } else {
503            numerator / denominator
504        }
505    }
506
507    fn calculate_variance(&self, values: &[f64], mean: f64) -> f64 {
508        if values.len() < 2 {
509            return 0.0;
510        }
511
512        let variance =
513            values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64;
514
515        variance
516    }
517
518    fn generate_summary(&self, records: &[&PerformanceRecord]) -> String {
519        if records.is_empty() {
520            return "No data available for the specified period.".to_string();
521        }
522
523        let total_duration: u64 = records.iter().map(|r| r.duration_ns).sum();
524        let avg_duration = total_duration / records.len() as u64;
525
526        let architectures: HashSet<&String> = records.iter().map(|r| &r.architecture).collect();
527
528        format!(
529            "Period summary: {} benchmarks across {} architectures. Average duration: {}ns",
530            records.len(),
531            architectures.len(),
532            avg_duration
533        )
534    }
535}
536
537/// Performance trend analysis result
538#[derive(Debug)]
539pub enum PerformanceTrend {
540    NoData,
541    Data {
542        operation: String,
543        data_points: usize,
544        trend_slope: f64,
545        avg_duration_ns: u64,
546        min_duration_ns: u64,
547        max_duration_ns: u64,
548        variance: f64,
549    },
550}
551
552/// Comprehensive performance report
553#[derive(Debug)]
554pub struct PerformanceReport {
555    pub period_days: u64,
556    pub total_benchmarks: usize,
557    pub unique_operations: usize,
558    pub trends: HashMap<String, PerformanceTrend>,
559    pub summary: String,
560}
561
562impl PerformanceReport {
563    /// Format the report as a string
564    pub fn format_report(&self) -> String {
565        let mut report = String::new();
566
567        report.push_str("=== Performance Report ===\n");
568        report.push_str(&format!("Period: {} days\n", self.period_days));
569        report.push_str(&format!("Total benchmarks: {}\n", self.total_benchmarks));
570        report.push_str(&format!("Unique operations: {}\n", self.unique_operations));
571        report.push_str(&format!("Summary: {}\n\n", self.summary));
572
573        report.push_str("Trends by operation:\n");
574        for (operation, trend) in &self.trends {
575            match trend {
576                PerformanceTrend::NoData => {
577                    report.push_str(&format!("  {}: No data\n", operation));
578                }
579                PerformanceTrend::Data {
580                    data_points,
581                    trend_slope,
582                    avg_duration_ns,
583                    ..
584                } => {
585                    let trend_direction = if *trend_slope > 1000.0 {
586                        "SLOWER"
587                    } else if *trend_slope < -1000.0 {
588                        "FASTER"
589                    } else {
590                        "STABLE"
591                    };
592
593                    report.push_str(&format!(
594                        "  {}: {} ({} data points, avg: {}ns, trend: {})\n",
595                        operation, trend_direction, data_points, avg_duration_ns, trend_direction
596                    ));
597                }
598            }
599        }
600
601        report.push_str("\n=== End Report ===\n");
602        report
603    }
604}
605
606/// Continuous integration integration
607pub struct CIIntegration;
608
609impl CIIntegration {
610    /// Run performance tests suitable for CI
611    #[cfg(not(feature = "no-std"))]
612    pub fn run_ci_benchmarks() -> Result<Vec<BenchmarkResult>, Box<dyn std::error::Error>> {
613        let mut suite = BenchmarkSuite::new();
614        let mut results = Vec::new();
615
616        let data: Vec<f32> = (0..2048).map(|i| i as f32 + 1.0).collect();
617        let mut scratch = vec![0.0f32; data.len()];
618
619        results.push(suite.benchmark("ci_dot_product", 200, || {
620            let _ = crate::vector::basic_operations::dot_product(&data, &data);
621        }));
622
623        results.push(suite.benchmark("ci_norm_l2", 200, || {
624            let _ = crate::vector::statistics_ops::norm_l2(&data);
625        }));
626
627        results.push(suite.benchmark("ci_reciprocal", 100, || {
628            crate::vector::math_functions::reciprocal_vec(&data, &mut scratch);
629        }));
630
631        Ok(results)
632    }
633
634    #[cfg(feature = "no-std")]
635    pub fn run_ci_benchmarks() -> Result<Vec<BenchmarkResult>, crate::traits::SimdError> {
636        let mut suite = BenchmarkSuite::new();
637        let mut results = Vec::new();
638
639        // Quick benchmarks suitable for CI
640        let test_data = (0..1000).map(|i| i as f32).collect::<Vec<f32>>();
641
642        results.push(suite.benchmark("ci_dot_product", 100, || {
643            let _result = crate::vector::dot_product(&test_data, &test_data);
644        }));
645
646        results.push(suite.benchmark("ci_euclidean_distance", 100, || {
647            let _result = crate::distance::euclidean_distance(&test_data, &test_data);
648        }));
649
650        Ok(results)
651    }
652
653    /// Check if performance is acceptable for CI
654    pub fn check_ci_performance(monitor: &PerformanceMonitor, results: &[BenchmarkResult]) -> bool {
655        let alerts = monitor.check_alerts(results);
656
657        // Fail CI if there are critical alerts
658        !alerts
659            .iter()
660            .any(|alert| alert.severity == AlertSeverity::Critical)
661    }
662
663    /// Generate CI performance summary
664    pub fn generate_ci_summary(alerts: &[PerformanceAlert]) -> String {
665        if alerts.is_empty() {
666            "✅ No performance regressions detected".to_string()
667        } else {
668            let critical_count = alerts
669                .iter()
670                .filter(|a| a.severity == AlertSeverity::Critical)
671                .count();
672            let error_count = alerts
673                .iter()
674                .filter(|a| a.severity == AlertSeverity::Error)
675                .count();
676            let warning_count = alerts
677                .iter()
678                .filter(|a| a.severity == AlertSeverity::Warning)
679                .count();
680
681            format!(
682                "⚠️ Performance alerts: {} critical, {} errors, {} warnings",
683                critical_count, error_count, warning_count
684            )
685        }
686    }
687}
688
689#[allow(non_snake_case)]
690#[cfg(all(test, not(feature = "no-std")))]
691mod tests {
692    use super::*;
693    #[cfg(not(feature = "no-std"))]
694    use std::fs;
695    #[cfg(not(feature = "no-std"))]
696    use std::time::Duration;
697
698    #[test]
699    #[cfg(not(feature = "no-std"))]
700    fn test_performance_monitor_creation() {
701        let temp_path = std::env::temp_dir().join("test_perf_monitor.csv");
702        let temp_file = temp_path.to_string_lossy().into_owned();
703        let _ = fs::remove_file(&temp_file); // Clean up if exists
704
705        let monitor = PerformanceMonitor::new(&temp_file);
706        assert!(monitor.is_ok());
707
708        let _ = fs::remove_file(&temp_file); // Clean up
709    }
710
711    #[test]
712    #[cfg(not(feature = "no-std"))]
713    fn test_performance_record_parsing() {
714        let temp_path = std::env::temp_dir().join("test_perf_parsing.csv");
715        let temp_file = temp_path.to_string_lossy().into_owned();
716        let _ = fs::remove_file(&temp_file);
717
718        let mut monitor = PerformanceMonitor::new(&temp_file).expect("operation should succeed");
719
720        let test_results = vec![BenchmarkResult {
721            name: "test_op".to_string(),
722            duration: Duration::from_millis(10),
723            throughput: Some(1000.0),
724            simd_width: 8,
725            architecture: "AVX2".to_string(),
726            iterations: 1000,
727        }];
728
729        let result = monitor.record_results(&test_results, Some("abc123".to_string()));
730        assert!(result.is_ok());
731
732        let _ = fs::remove_file(&temp_file);
733    }
734
735    #[test]
736    #[cfg(not(feature = "no-std"))]
737    fn test_trend_analysis() {
738        let temp_path = std::env::temp_dir().join("test_trend_analysis.csv");
739        let temp_file = temp_path.to_string_lossy().into_owned();
740        let _ = fs::remove_file(&temp_file);
741
742        let monitor = PerformanceMonitor::new(&temp_file).expect("operation should succeed");
743        let trend = monitor.analyze_trends("nonexistent_op", 7);
744
745        match trend {
746            PerformanceTrend::NoData => {
747                // Expected for empty data
748            }
749            _ => panic!("Expected NoData for empty dataset"),
750        }
751
752        let _ = fs::remove_file(&temp_file);
753    }
754
755    #[test]
756    #[cfg(not(feature = "no-std"))]
757    fn test_ci_integration() {
758        let results = CIIntegration::run_ci_benchmarks();
759        assert!(results.is_ok());
760
761        let results = results.expect("operation should succeed");
762        assert!(!results.is_empty());
763
764        for result in &results {
765            assert!(result.duration > Duration::from_nanos(0));
766        }
767    }
768
769    #[test]
770    fn test_performance_thresholds() {
771        let thresholds = PerformanceThresholds::default();
772        assert_eq!(thresholds.regression_threshold, 5.0);
773        assert_eq!(thresholds.improvement_threshold, 10.0);
774        assert_eq!(thresholds.critical_slowdown, 25.0);
775    }
776}