1use scirs2_core::ndarray::Array2;
9use sklears_core::error::SklearsError;
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18pub type PerformanceResult<T> = Result<T, SklearsError>;
20
21#[derive(Debug, Clone, PartialEq)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct MemoryStats {
25 pub peak_usage_bytes: u64,
27 pub current_usage_bytes: u64,
29 pub allocated_bytes: u64,
31 pub deallocated_bytes: u64,
33 pub allocation_count: u64,
35 pub deallocation_count: u64,
37}
38
39impl MemoryStats {
40 pub fn new() -> Self {
42 Self {
43 peak_usage_bytes: 0,
44 current_usage_bytes: 0,
45 allocated_bytes: 0,
46 deallocated_bytes: 0,
47 allocation_count: 0,
48 deallocation_count: 0,
49 }
50 }
51
52 pub fn efficiency_ratio(&self) -> f64 {
54 if self.allocated_bytes > 0 {
55 self.deallocated_bytes as f64 / self.allocated_bytes as f64
56 } else {
57 1.0
58 }
59 }
60
61 pub fn avg_allocation_size(&self) -> f64 {
63 if self.allocation_count > 0 {
64 self.allocated_bytes as f64 / self.allocation_count as f64
65 } else {
66 0.0
67 }
68 }
69}
70
71impl Default for MemoryStats {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77#[derive(Debug, Clone)]
79#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
80pub struct PerformanceMetrics {
81 pub execution_time: Duration,
83 pub memory_stats: MemoryStats,
85 pub ops_per_second: f64,
87 pub samples_per_second: f64,
89 pub cpu_usage_percent: f64,
91 pub gpu_usage_percent: Option<f64>,
93 pub custom_metrics: HashMap<String, f64>,
95}
96
97impl PerformanceMetrics {
98 pub fn new(execution_time: Duration, memory_stats: MemoryStats) -> Self {
100 Self {
101 execution_time,
102 memory_stats,
103 ops_per_second: 0.0,
104 samples_per_second: 0.0,
105 cpu_usage_percent: 0.0,
106 gpu_usage_percent: None,
107 custom_metrics: HashMap::new(),
108 }
109 }
110
111 pub fn with_ops_per_second(mut self, ops_per_second: f64) -> Self {
113 self.ops_per_second = ops_per_second;
114 self
115 }
116
117 pub fn with_samples_per_second(mut self, samples_per_second: f64) -> Self {
119 self.samples_per_second = samples_per_second;
120 self
121 }
122
123 pub fn with_cpu_usage(mut self, cpu_usage_percent: f64) -> Self {
125 self.cpu_usage_percent = cpu_usage_percent;
126 self
127 }
128
129 pub fn with_gpu_usage(mut self, gpu_usage_percent: f64) -> Self {
131 self.gpu_usage_percent = Some(gpu_usage_percent);
132 self
133 }
134
135 pub fn add_custom_metric(&mut self, name: String, value: f64) {
137 self.custom_metrics.insert(name, value);
138 }
139}
140
141#[derive(Debug, Clone)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub struct BenchmarkComparison {
145 pub benchmark_name: String,
147 pub baseline: PerformanceMetrics,
149 pub current: PerformanceMetrics,
151 pub performance_ratio: f64,
153 pub memory_ratio: f64,
155 pub regression_detected: bool,
157 pub improvement_percent: f64,
159}
160
161impl BenchmarkComparison {
162 pub fn new(
164 benchmark_name: String,
165 baseline: PerformanceMetrics,
166 current: PerformanceMetrics,
167 ) -> Self {
168 let baseline_time = baseline.execution_time.as_secs_f64();
169 let current_time = current.execution_time.as_secs_f64();
170
171 let performance_ratio = if baseline_time > 0.0 {
172 current_time / baseline_time
173 } else {
174 1.0
175 };
176
177 let memory_ratio = if baseline.memory_stats.peak_usage_bytes > 0 {
178 current.memory_stats.peak_usage_bytes as f64
179 / baseline.memory_stats.peak_usage_bytes as f64
180 } else {
181 1.0
182 };
183
184 let improvement_percent = (1.0 - performance_ratio) * 100.0;
185 let regression_detected = performance_ratio > 1.1; Self {
188 benchmark_name,
189 baseline,
190 current,
191 performance_ratio,
192 memory_ratio,
193 regression_detected,
194 improvement_percent,
195 }
196 }
197
198 pub fn has_improvement(&self) -> bool {
200 self.improvement_percent > 5.0
201 }
202
203 pub fn has_memory_regression(&self) -> bool {
205 self.memory_ratio > 1.2
206 }
207}
208
209pub struct PerformanceProfiler {
211 start_time: Option<Instant>,
212 memory_tracker: Arc<Mutex<MemoryStats>>,
213 operation_count: u64,
214 sample_count: u64,
215}
216
217impl PerformanceProfiler {
218 pub fn new() -> Self {
220 Self {
221 start_time: None,
222 memory_tracker: Arc::new(Mutex::new(MemoryStats::new())),
223 operation_count: 0,
224 sample_count: 0,
225 }
226 }
227
228 pub fn start(&mut self) {
230 self.start_time = Some(Instant::now());
231 }
232
233 pub fn stop(&self) -> PerformanceResult<PerformanceMetrics> {
235 let start_time = self
236 .start_time
237 .ok_or_else(|| SklearsError::InvalidParameter {
238 name: "profiler_state".to_string(),
239 reason: "Profiler was not started".to_string(),
240 })?;
241
242 let execution_time = start_time.elapsed();
243 let memory_stats = self
244 .memory_tracker
245 .lock()
246 .map_err(|_| SklearsError::InvalidParameter {
247 name: "memory_tracker".to_string(),
248 reason: "Failed to acquire memory tracker lock".to_string(),
249 })?
250 .clone();
251
252 let ops_per_second = if execution_time.as_secs_f64() > 0.0 {
253 self.operation_count as f64 / execution_time.as_secs_f64()
254 } else {
255 0.0
256 };
257
258 let samples_per_second = if execution_time.as_secs_f64() > 0.0 {
259 self.sample_count as f64 / execution_time.as_secs_f64()
260 } else {
261 0.0
262 };
263
264 Ok(PerformanceMetrics::new(execution_time, memory_stats)
265 .with_ops_per_second(ops_per_second)
266 .with_samples_per_second(samples_per_second))
267 }
268
269 pub fn record_operation(&mut self) {
271 self.operation_count += 1;
272 }
273
274 pub fn record_operations(&mut self, count: u64) {
276 self.operation_count += count;
277 }
278
279 pub fn record_samples(&mut self, count: u64) {
281 self.sample_count += count;
282 }
283
284 pub fn record_allocation(&self, bytes: u64) {
286 if let Ok(mut stats) = self.memory_tracker.lock() {
287 stats.allocated_bytes += bytes;
288 stats.allocation_count += 1;
289 stats.current_usage_bytes += bytes;
290
291 if stats.current_usage_bytes > stats.peak_usage_bytes {
292 stats.peak_usage_bytes = stats.current_usage_bytes;
293 }
294 }
295 }
296
297 pub fn record_deallocation(&self, bytes: u64) {
299 if let Ok(mut stats) = self.memory_tracker.lock() {
300 stats.deallocated_bytes += bytes;
301 stats.deallocation_count += 1;
302 stats.current_usage_bytes = stats.current_usage_bytes.saturating_sub(bytes);
303 }
304 }
305}
306
307impl Default for PerformanceProfiler {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313pub struct BenchmarkSuite {
315 benchmarks: HashMap<String, Box<dyn Fn() -> PerformanceResult<PerformanceMetrics>>>,
316 baselines: HashMap<String, PerformanceMetrics>,
317 results: HashMap<String, BenchmarkComparison>,
318}
319
320impl BenchmarkSuite {
321 pub fn new() -> Self {
323 Self {
324 benchmarks: HashMap::new(),
325 baselines: HashMap::new(),
326 results: HashMap::new(),
327 }
328 }
329
330 pub fn add_benchmark<F>(&mut self, name: String, benchmark_fn: F)
332 where
333 F: Fn() -> PerformanceResult<PerformanceMetrics> + 'static,
334 {
335 self.benchmarks.insert(name, Box::new(benchmark_fn));
336 }
337
338 pub fn set_baseline(&mut self, name: String, baseline: PerformanceMetrics) {
340 self.baselines.insert(name, baseline);
341 }
342
343 pub fn run_all(&mut self) -> PerformanceResult<()> {
345 for (name, benchmark_fn) in &self.benchmarks {
346 let current_metrics = benchmark_fn()?;
347
348 if let Some(baseline) = self.baselines.get(name) {
349 let comparison =
350 BenchmarkComparison::new(name.clone(), baseline.clone(), current_metrics);
351 self.results.insert(name.clone(), comparison);
352 }
353 }
354 Ok(())
355 }
356
357 pub fn run_benchmark(&mut self, name: &str) -> PerformanceResult<()> {
359 if let Some(benchmark_fn) = self.benchmarks.get(name) {
360 let current_metrics = benchmark_fn()?;
361
362 if let Some(baseline) = self.baselines.get(name) {
363 let comparison =
364 BenchmarkComparison::new(name.to_string(), baseline.clone(), current_metrics);
365 self.results.insert(name.to_string(), comparison);
366 }
367 } else {
368 return Err(SklearsError::InvalidParameter {
369 name: "benchmark_name".to_string(),
370 reason: format!("Benchmark '{}' not found", name),
371 });
372 }
373 Ok(())
374 }
375
376 pub fn get_results(&self) -> &HashMap<String, BenchmarkComparison> {
378 &self.results
379 }
380
381 pub fn has_regressions(&self) -> bool {
383 self.results.values().any(|r| r.regression_detected)
384 }
385
386 pub fn get_regressions(&self) -> Vec<&BenchmarkComparison> {
388 self.results
389 .values()
390 .filter(|r| r.regression_detected)
391 .collect()
392 }
393
394 pub fn generate_report(&self) -> PerformanceReport {
396 let total_benchmarks = self.results.len();
397 let regressions = self.get_regressions();
398 let regression_count = regressions.len();
399
400 let improvements: Vec<&BenchmarkComparison> = self
401 .results
402 .values()
403 .filter(|r| r.has_improvement())
404 .collect();
405 let improvement_count = improvements.len();
406
407 let memory_regressions: Vec<&BenchmarkComparison> = self
408 .results
409 .values()
410 .filter(|r| r.has_memory_regression())
411 .collect();
412 let memory_regression_count = memory_regressions.len();
413
414 PerformanceReport {
415 total_benchmarks,
416 regression_count,
417 improvement_count,
418 memory_regression_count,
419 overall_status: if regression_count == 0 {
420 PerformanceStatus::Pass
421 } else {
422 PerformanceStatus::Fail
423 },
424 regressions: regressions.into_iter().cloned().collect(),
425 improvements: improvements.into_iter().cloned().collect(),
426 memory_regressions: memory_regressions.into_iter().cloned().collect(),
427 }
428 }
429}
430
431impl Default for BenchmarkSuite {
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437#[derive(Debug, Clone, PartialEq)]
439#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
440pub enum PerformanceStatus {
441 Pass,
443 Fail,
445 Warning,
447}
448
449#[derive(Debug, Clone)]
451#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
452pub struct PerformanceReport {
453 pub total_benchmarks: usize,
455 pub regression_count: usize,
457 pub improvement_count: usize,
459 pub memory_regression_count: usize,
461 pub overall_status: PerformanceStatus,
463 pub regressions: Vec<BenchmarkComparison>,
465 pub improvements: Vec<BenchmarkComparison>,
467 pub memory_regressions: Vec<BenchmarkComparison>,
469}
470
471impl fmt::Display for PerformanceReport {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 writeln!(f, "=== Performance Test Report ===")?;
474 writeln!(f, "Total Benchmarks: {}", self.total_benchmarks)?;
475 writeln!(f, "Overall Status: {:?}", self.overall_status)?;
476 writeln!(f)?;
477
478 writeln!(f, "Improvements: {} benchmarks", self.improvement_count)?;
479 for improvement in &self.improvements {
480 writeln!(
481 f,
482 " {}: {:.1}% faster",
483 improvement.benchmark_name, improvement.improvement_percent
484 )?;
485 }
486 writeln!(f)?;
487
488 writeln!(
489 f,
490 "Performance Regressions: {} benchmarks",
491 self.regression_count
492 )?;
493 for regression in &self.regressions {
494 writeln!(
495 f,
496 " {} {:.1}% slower (ratio: {:.2})",
497 regression.benchmark_name,
498 -regression.improvement_percent,
499 regression.performance_ratio
500 )?;
501 }
502 writeln!(f)?;
503
504 writeln!(
505 f,
506 "Memory Regressions: {} benchmarks",
507 self.memory_regression_count
508 )?;
509 for mem_regression in &self.memory_regressions {
510 writeln!(
511 f,
512 " {}: {:.1}% more memory (ratio: {:.2})",
513 mem_regression.benchmark_name,
514 (mem_regression.memory_ratio - 1.0) * 100.0,
515 mem_regression.memory_ratio
516 )?;
517 }
518
519 Ok(())
520 }
521}
522
523#[allow(dead_code)] pub struct MemoryLeakDetector {
526 initial_memory: u64,
527 allocations: HashMap<usize, u64>,
528 allocation_tracker: Arc<Mutex<u64>>,
529}
530
531impl MemoryLeakDetector {
532 pub fn new() -> Self {
534 Self {
535 initial_memory: Self::get_current_memory_usage(),
536 allocations: HashMap::new(),
537 allocation_tracker: Arc::new(Mutex::new(0)),
538 }
539 }
540
541 pub fn start_monitoring(&mut self) {
543 self.initial_memory = Self::get_current_memory_usage();
544 self.allocations.clear();
545 }
546
547 pub fn check_for_leaks(&self) -> MemoryLeakReport {
549 let current_memory = Self::get_current_memory_usage();
550 let memory_increase = current_memory.saturating_sub(self.initial_memory);
551
552 let leak_threshold = 1024 * 1024; let has_leak = memory_increase > leak_threshold;
554
555 let active_allocations = self.allocations.len();
556 let total_allocated = self.allocations.values().sum::<u64>();
557
558 MemoryLeakReport {
559 initial_memory: self.initial_memory,
560 current_memory,
561 memory_increase,
562 has_leak,
563 leak_threshold,
564 active_allocations,
565 total_allocated,
566 }
567 }
568
569 pub fn track_allocation(&mut self, ptr: usize, size: u64) {
571 self.allocations.insert(ptr, size);
572 }
573
574 pub fn track_deallocation(&mut self, ptr: usize) {
576 self.allocations.remove(&ptr);
577 }
578
579 fn get_current_memory_usage() -> u64 {
581 std::process::id() as u64 * 1024 }
586}
587
588impl Default for MemoryLeakDetector {
589 fn default() -> Self {
590 Self::new()
591 }
592}
593
594#[derive(Debug, Clone)]
596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
597pub struct MemoryLeakReport {
598 pub initial_memory: u64,
600 pub current_memory: u64,
602 pub memory_increase: u64,
604 pub has_leak: bool,
606 pub leak_threshold: u64,
608 pub active_allocations: usize,
610 pub total_allocated: u64,
612}
613
614impl fmt::Display for MemoryLeakReport {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 writeln!(f, "=== Memory Leak Detection Report ===")?;
617 writeln!(f, "Initial Memory: {} bytes", self.initial_memory)?;
618 writeln!(f, "Current Memory: {} bytes", self.current_memory)?;
619 writeln!(f, "Memory Increase: {} bytes", self.memory_increase)?;
620 writeln!(f, "Leak Detected: {}", self.has_leak)?;
621 writeln!(f, "Active Allocations: {}", self.active_allocations)?;
622 writeln!(f, "Total Allocated: {} bytes", self.total_allocated)?;
623
624 if self.has_leak {
625 writeln!(
626 f,
627 "⚠️ Memory leak detected! Increase exceeds threshold of {} bytes",
628 self.leak_threshold
629 )?;
630 } else {
631 writeln!(f, "✅ No memory leaks detected")?;
632 }
633
634 Ok(())
635 }
636}
637
638pub mod utils {
640 use super::*;
641
642 pub fn benchmark_function<F, T>(f: F, iterations: u32) -> PerformanceResult<PerformanceMetrics>
644 where
645 F: Fn() -> T,
646 {
647 let mut profiler = PerformanceProfiler::new();
648 profiler.start();
649
650 for _ in 0..iterations {
651 let _ = f();
652 profiler.record_operation();
653 }
654
655 profiler.stop()
656 }
657
658 pub fn benchmark_matrix_multiply(
660 a: &Array2<f64>,
661 b: &Array2<f64>,
662 iterations: u32,
663 ) -> PerformanceResult<PerformanceMetrics> {
664 benchmark_function(|| a.dot(b), iterations)
665 }
666
667 pub fn benchmark_forward_pass<F>(
669 forward_fn: F,
670 input: &Array2<f64>,
671 iterations: u32,
672 ) -> PerformanceResult<PerformanceMetrics>
673 where
674 F: Fn(&Array2<f64>) -> Array2<f64>,
675 {
676 let mut profiler = PerformanceProfiler::new();
677 profiler.start();
678
679 for _ in 0..iterations {
680 let _ = forward_fn(input);
681 profiler.record_operation();
682 profiler.record_samples(input.nrows() as u64);
683 }
684
685 profiler.stop()
686 }
687
688 pub fn create_standard_benchmark_suite() -> BenchmarkSuite {
690 let mut suite = BenchmarkSuite::new();
691
692 suite.add_benchmark("matrix_multiply_100x100".to_string(), || {
694 let a = Array2::ones((100, 100));
695 let b = Array2::ones((100, 100));
696 benchmark_matrix_multiply(&a, &b, 1000)
697 });
698
699 suite.add_benchmark("matrix_multiply_1000x1000".to_string(), || {
701 let a = Array2::ones((1000, 1000));
702 let b = Array2::ones((1000, 1000));
703 benchmark_matrix_multiply(&a, &b, 10)
704 });
705
706 suite
707 }
708}
709
710#[allow(non_snake_case)]
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use approx;
715
716 #[test]
717 fn test_memory_stats() {
718 let mut stats = MemoryStats::new();
719 stats.allocated_bytes = 1000;
720 stats.deallocated_bytes = 800;
721 stats.allocation_count = 10;
722
723 assert_eq!(stats.efficiency_ratio(), 0.8);
724 assert_eq!(stats.avg_allocation_size(), 100.0);
725 }
726
727 #[test]
728 fn test_performance_profiler() {
729 let mut profiler = PerformanceProfiler::new();
730 profiler.start();
731
732 std::thread::sleep(Duration::from_millis(10));
734 profiler.record_operation();
735 profiler.record_samples(100);
736
737 let metrics = profiler.stop().expect("operation should succeed");
738 assert!(metrics.execution_time >= Duration::from_millis(10));
739 assert!(metrics.ops_per_second > 0.0);
740 assert!(metrics.samples_per_second > 0.0);
741 }
742
743 #[test]
744 fn test_benchmark_comparison() {
745 let baseline = PerformanceMetrics::new(Duration::from_millis(100), MemoryStats::new());
746
747 let improved = PerformanceMetrics::new(Duration::from_millis(80), MemoryStats::new());
748
749 let comparison = BenchmarkComparison::new("test_benchmark".to_string(), baseline, improved);
750
751 assert!(comparison.has_improvement());
752 assert!(!comparison.regression_detected);
753 approx::assert_abs_diff_eq!(comparison.improvement_percent, 20.0, epsilon = 1e-10);
754 }
755
756 #[test]
757 fn test_memory_leak_detector() {
758 let mut detector = MemoryLeakDetector::new();
759 detector.start_monitoring();
760
761 detector.track_allocation(0x1000, 1024);
763 detector.track_allocation(0x2000, 2048);
764
765 let report = detector.check_for_leaks();
766 assert_eq!(report.active_allocations, 2);
767 assert_eq!(report.total_allocated, 3072);
768
769 detector.track_deallocation(0x1000);
771 let report = detector.check_for_leaks();
772 assert_eq!(report.active_allocations, 1);
773 assert_eq!(report.total_allocated, 2048);
774 }
775
776 #[test]
777 fn test_benchmark_suite() {
778 let mut suite = BenchmarkSuite::new();
779
780 suite.add_benchmark("simple_add".to_string(), || {
782 let mut profiler = PerformanceProfiler::new();
783 profiler.start();
784
785 let _ = 1 + 1;
786 profiler.record_operation();
787
788 profiler.stop()
789 });
790
791 let baseline = PerformanceMetrics::new(Duration::from_millis(1), MemoryStats::new());
793 suite.set_baseline("simple_add".to_string(), baseline);
794
795 suite
797 .run_benchmark("simple_add")
798 .expect("operation should succeed");
799
800 let results = suite.get_results();
801 assert!(results.contains_key("simple_add"));
802 }
803
804 #[test]
805 fn test_benchmark_utilities() {
806 let result = utils::benchmark_function(
807 || {
808 let a = Array2::<f64>::ones((10, 10));
809 let b = Array2::<f64>::ones((10, 10));
810 a.dot(&b)
811 },
812 100,
813 );
814
815 assert!(result.is_ok());
816 let metrics = result.expect("operation should succeed");
817 assert!(metrics.ops_per_second > 0.0);
818 }
819}