1use scirs2_core::ndarray::Array2;
7use sklears_core::{
8 error::Result as SklResult, prelude::SklearsError, traits::Estimator, types::Float,
9};
10use std::collections::HashMap;
11use std::time::{Duration, SystemTime, UNIX_EPOCH};
12
13pub struct PipelineDebugger {
15 execution_trace: Vec<ExecutionStep>,
17 breakpoints: HashMap<String, Breakpoint>,
19 config: DebugConfig,
21 error_tracker: ErrorTracker,
23 profiler: PerformanceProfiler,
25 execution_state: ExecutionState,
27}
28
29#[derive(Clone, Debug)]
31pub struct DebugConfig {
32 pub step_by_step: bool,
34 pub enable_profiling: bool,
36 pub track_memory: bool,
38 pub capture_data_snapshots: bool,
40 pub max_snapshot_size: usize,
42 pub log_level: DebugLogLevel,
44 pub output_format: DebugOutputFormat,
46}
47
48#[derive(Clone, Debug)]
50pub enum DebugLogLevel {
51 Trace,
53 Debug,
55 Info,
57 Warn,
59 Error,
61}
62
63#[derive(Clone, Debug)]
65pub enum DebugOutputFormat {
66 Console,
68 Json,
70 Html,
72 Graphviz,
74}
75
76#[derive(Clone, Debug)]
78pub struct ExecutionStep {
79 pub step_id: String,
81 pub component_name: String,
83 pub timestamp: SystemTime,
85 pub duration: Duration,
87 pub input_summary: DataSummary,
89 pub output_summary: DataSummary,
91 pub memory_usage: MemoryUsage,
93 pub error: Option<StepError>,
95 pub metadata: HashMap<String, String>,
97}
98
99#[derive(Clone, Debug)]
101pub struct DataSummary {
102 pub shape: Vec<usize>,
104 pub data_type: String,
106 pub statistics: Option<StatisticalSummary>,
108 pub snapshot: Option<DataSnapshot>,
110}
111
112#[derive(Clone, Debug)]
114pub struct StatisticalSummary {
115 pub mean: Vec<f64>,
117 pub std: Vec<f64>,
119 pub min: Vec<f64>,
121 pub max: Vec<f64>,
123 pub null_count: usize,
125 pub total_count: usize,
127}
128
129#[derive(Clone, Debug)]
131pub struct DataSnapshot {
132 pub sample_values: Vec<Vec<f64>>,
134 pub sample_indices: Vec<usize>,
136 pub is_complete: bool,
138}
139
140#[derive(Clone, Debug)]
142pub struct MemoryUsage {
143 pub component_memory: u64,
145 pub heap_memory: u64,
147 pub peak_memory: u64,
149 pub allocations: u64,
151 pub deallocations: u64,
153}
154
155#[derive(Clone, Debug)]
157pub struct StepError {
158 pub error_type: String,
160 pub message: String,
162 pub stack_trace: Option<String>,
164 pub context: HashMap<String, String>,
166 pub suggested_fixes: Vec<String>,
168}
169
170#[derive(Clone, Debug)]
172pub struct Breakpoint {
173 pub id: String,
175 pub component_name: String,
177 pub condition: BreakpointCondition,
179 pub frequency: BreakpointFrequency,
181 pub hit_count: usize,
183 pub enabled: bool,
185}
186
187#[derive(Clone, Debug)]
189pub enum BreakpointCondition {
190 Always,
192 OnError,
194 OnDataCondition {
196 field: String,
197 operator: ComparisonOperator,
198 value: f64,
199 },
200 OnPerformanceCondition {
202 metric: PerformanceMetric,
203 operator: ComparisonOperator,
204 value: f64,
205 },
206 Custom { expression: String },
208}
209
210#[derive(Clone, Debug)]
212pub enum ComparisonOperator {
213 Equal,
215 NotEqual,
217 GreaterThan,
219 LessThan,
221 GreaterThanOrEqual,
223 LessThanOrEqual,
225}
226
227#[derive(Clone, Debug)]
229pub enum PerformanceMetric {
230 ExecutionTime,
232 MemoryUsage,
234 CpuUsage,
236 ThroughputMbps,
238}
239
240#[derive(Clone, Debug)]
242pub enum BreakpointFrequency {
243 Always,
245 Once,
247 EveryNHits(usize),
249}
250
251pub struct ErrorTracker {
253 errors: Vec<TrackedError>,
255 patterns: HashMap<String, ErrorPattern>,
257 statistics: ErrorStatistics,
259}
260
261#[derive(Clone, Debug)]
263pub struct TrackedError {
264 pub timestamp: SystemTime,
266 pub component: String,
268 pub error: StepError,
270 pub recovery_actions: Vec<String>,
272 pub resolution_status: ErrorResolutionStatus,
274}
275
276#[derive(Clone, Debug)]
278pub enum ErrorResolutionStatus {
279 Unresolved,
281 Resolved,
283 WorkedAround,
285 Ignored,
287}
288
289#[derive(Clone, Debug)]
291pub struct ErrorPattern {
292 pub pattern_id: String,
294 pub error_type_pattern: String,
296 pub component_pattern: String,
298 pub frequency: usize,
300 pub common_fixes: Vec<String>,
302}
303
304#[derive(Clone, Debug)]
306pub struct ErrorStatistics {
307 pub total_errors: usize,
309 pub errors_by_type: HashMap<String, usize>,
311 pub errors_by_component: HashMap<String, usize>,
313 pub resolution_rate: f64,
315 pub mean_resolution_time: Duration,
317}
318
319pub struct PerformanceProfiler {
321 measurements: Vec<PerformanceMeasurement>,
323 bottleneck_detector: BottleneckDetector,
325 config: ProfilerConfig,
327}
328
329#[derive(Clone, Debug)]
331pub struct PerformanceMeasurement {
332 pub component: String,
334 pub execution_time: Duration,
336 pub cpu_usage: f64,
338 pub memory_usage: MemoryUsage,
340 pub io_operations: IoStatistics,
342 pub custom_metrics: HashMap<String, f64>,
344}
345
346#[derive(Clone, Debug)]
348pub struct IoStatistics {
349 pub bytes_read: u64,
351 pub bytes_written: u64,
353 pub read_operations: u64,
355 pub write_operations: u64,
357 pub read_latency: Duration,
359 pub write_latency: Duration,
361}
362
363pub struct BottleneckDetector {
365 bottlenecks: Vec<Bottleneck>,
367 thresholds: BottleneckThresholds,
369 analysis_history: Vec<BottleneckAnalysis>,
371}
372
373#[derive(Clone, Debug)]
375pub struct Bottleneck {
376 pub bottleneck_type: BottleneckType,
378 pub component: String,
380 pub severity: BottleneckSeverity,
382 pub impact: PerformanceImpact,
384 pub optimizations: Vec<String>,
386 pub detected_at: SystemTime,
388}
389
390#[derive(Clone, Debug)]
392pub enum BottleneckType {
393 Cpu,
395 Memory,
397 Io,
399 Network,
401 Algorithm,
403 DataProcessing,
405}
406
407#[derive(Clone, Debug)]
409pub enum BottleneckSeverity {
410 Low,
412 Medium,
414 High,
416 Critical,
418}
419
420#[derive(Clone, Debug)]
422pub struct PerformanceImpact {
423 pub slowdown_factor: f64,
425 pub time_impact: Duration,
427 pub memory_overhead: u64,
429 pub throughput_impact: f64,
431}
432
433#[derive(Clone, Debug)]
435pub struct BottleneckThresholds {
436 pub cpu_threshold: f64,
438 pub memory_threshold: u64,
440 pub time_threshold: Duration,
442 pub io_latency_threshold: Duration,
444}
445
446#[derive(Clone, Debug)]
448pub struct BottleneckAnalysis {
449 pub timestamp: SystemTime,
451 pub components: Vec<String>,
453 pub bottlenecks: Vec<Bottleneck>,
455 pub performance_score: f64,
457 pub recommendations: Vec<String>,
459}
460
461#[derive(Clone, Debug)]
463pub struct ProfilerConfig {
464 pub enable_cpu_profiling: bool,
466 pub enable_memory_profiling: bool,
468 pub enable_io_profiling: bool,
470 pub sampling_frequency: f64,
472 pub max_profiling_duration: Option<Duration>,
474}
475
476#[derive(Clone, Debug)]
478pub enum ExecutionState {
479 Running,
481 Paused { component: String, reason: String },
483 Stepping,
485 Completed,
487 Failed { error: String },
489}
490
491impl Default for DebugConfig {
492 fn default() -> Self {
493 Self {
494 step_by_step: false,
495 enable_profiling: true,
496 track_memory: true,
497 capture_data_snapshots: false,
498 max_snapshot_size: 1000,
499 log_level: DebugLogLevel::Info,
500 output_format: DebugOutputFormat::Console,
501 }
502 }
503}
504
505impl Default for ProfilerConfig {
506 fn default() -> Self {
507 Self {
508 enable_cpu_profiling: true,
509 enable_memory_profiling: true,
510 enable_io_profiling: true,
511 sampling_frequency: 100.0,
512 max_profiling_duration: Some(Duration::from_secs(3600)), }
514 }
515}
516
517impl Default for BottleneckThresholds {
518 fn default() -> Self {
519 Self {
520 cpu_threshold: 80.0,
521 memory_threshold: 1024 * 1024 * 1024, time_threshold: Duration::from_secs(10),
523 io_latency_threshold: Duration::from_millis(100),
524 }
525 }
526}
527
528impl PipelineDebugger {
529 #[must_use]
531 pub fn new(config: DebugConfig) -> Self {
532 Self {
533 execution_trace: Vec::new(),
534 breakpoints: HashMap::new(),
535 error_tracker: ErrorTracker::new(),
536 profiler: PerformanceProfiler::new(ProfilerConfig::default()),
537 execution_state: ExecutionState::Running,
538 config,
539 }
540 }
541
542 pub fn add_breakpoint(&mut self, breakpoint: Breakpoint) {
544 self.breakpoints.insert(breakpoint.id.clone(), breakpoint);
545 }
546
547 pub fn remove_breakpoint(&mut self, breakpoint_id: &str) -> Option<Breakpoint> {
549 self.breakpoints.remove(breakpoint_id)
550 }
551
552 pub fn toggle_breakpoint(&mut self, breakpoint_id: &str, enabled: bool) -> SklResult<()> {
554 match self.breakpoints.get_mut(breakpoint_id) {
555 Some(breakpoint) => {
556 breakpoint.enabled = enabled;
557 Ok(())
558 }
559 None => Err(SklearsError::InvalidInput(format!(
560 "Breakpoint {breakpoint_id} not found"
561 ))),
562 }
563 }
564
565 pub fn start_debug_session(&mut self) -> SklResult<DebugSession> {
567 let session = DebugSession::new(self.config.clone());
568 self.execution_state = ExecutionState::Running;
569 Ok(session)
570 }
571
572 pub fn record_step(
574 &mut self,
575 step_id: &str,
576 component_name: &str,
577 duration: Duration,
578 input_data: Option<&Array2<Float>>,
579 output_data: Option<&Array2<Float>>,
580 error: Option<StepError>,
581 ) -> SklResult<()> {
582 let timestamp = SystemTime::now();
583
584 let input_summary = input_data
585 .map(|data| self.create_data_summary(data))
586 .unwrap_or_else(|| DataSummary {
587 shape: vec![],
588 data_type: "None".to_string(),
589 statistics: None,
590 snapshot: None,
591 });
592
593 let output_summary = output_data
594 .map(|data| self.create_data_summary(data))
595 .unwrap_or_else(|| DataSummary {
596 shape: vec![],
597 data_type: "None".to_string(),
598 statistics: None,
599 snapshot: None,
600 });
601
602 let memory_usage = self.measure_memory_usage();
603
604 let step = ExecutionStep {
605 step_id: step_id.to_string(),
606 component_name: component_name.to_string(),
607 timestamp,
608 duration,
609 input_summary,
610 output_summary,
611 memory_usage,
612 error: error.clone(),
613 metadata: HashMap::new(),
614 };
615
616 self.execution_trace.push(step);
617
618 self.check_breakpoints(component_name, &error)?;
620
621 if let Some(error) = error {
623 self.error_tracker.record_error(component_name, error);
624 }
625
626 if self.config.enable_profiling {
628 let measurement = PerformanceMeasurement {
629 component: component_name.to_string(),
630 execution_time: duration,
631 cpu_usage: self.measure_cpu_usage(),
632 memory_usage: self.measure_memory_usage(),
633 io_operations: self.measure_io_statistics(),
634 custom_metrics: HashMap::new(),
635 };
636 self.profiler.record_measurement(measurement);
637 }
638
639 Ok(())
640 }
641
642 fn check_breakpoints(
644 &mut self,
645 component_name: &str,
646 error: &Option<StepError>,
647 ) -> SklResult<()> {
648 for (id, breakpoint) in &mut self.breakpoints {
649 if !breakpoint.enabled {
650 continue;
651 }
652
653 let should_break = match &breakpoint.condition {
654 BreakpointCondition::Always => breakpoint.component_name == component_name,
655 BreakpointCondition::OnError => {
656 breakpoint.component_name == component_name && error.is_some()
657 }
658 BreakpointCondition::OnDataCondition { .. } => {
659 false
661 }
662 BreakpointCondition::OnPerformanceCondition { .. } => {
663 false
665 }
666 BreakpointCondition::Custom { .. } => {
667 false
669 }
670 };
671
672 if should_break {
673 breakpoint.hit_count += 1;
674
675 let should_pause = match breakpoint.frequency {
676 BreakpointFrequency::Always => true,
677 BreakpointFrequency::Once => breakpoint.hit_count == 1,
678 BreakpointFrequency::EveryNHits(n) => breakpoint.hit_count % n == 0,
679 };
680
681 if should_pause {
682 self.execution_state = ExecutionState::Paused {
683 component: component_name.to_string(),
684 reason: format!("Breakpoint {id} triggered"),
685 };
686 break;
687 }
688 }
689 }
690 Ok(())
691 }
692
693 fn create_data_summary(&self, data: &Array2<Float>) -> DataSummary {
695 let shape = data.shape().to_vec();
696 let data_type = "Array2<Float>".to_string();
697
698 let statistics = if data.is_empty() {
699 None
700 } else {
701 Some(self.compute_statistics(data))
702 };
703
704 let snapshot = if self.config.capture_data_snapshots {
705 Some(self.create_data_snapshot(data))
706 } else {
707 None
708 };
709
710 DataSummary {
712 shape,
713 data_type,
714 statistics,
715 snapshot,
716 }
717 }
718
719 fn compute_statistics(&self, data: &Array2<Float>) -> StatisticalSummary {
721 let flat_data: Vec<Float> = data.iter().copied().collect();
722 let total_count = flat_data.len();
723 let null_count = flat_data.iter().filter(|x| x.is_nan()).count();
724
725 let valid_data: Vec<Float> = flat_data.iter().filter(|x| !x.is_nan()).copied().collect();
726
727 if valid_data.is_empty() {
728 return StatisticalSummary {
729 mean: vec![],
730 std: vec![],
731 min: vec![],
732 max: vec![],
733 null_count,
734 total_count,
735 };
736 }
737
738 let mean = valid_data.iter().sum::<Float>() / valid_data.len() as Float;
739 let variance = valid_data.iter().map(|x| (x - mean).powi(2)).sum::<Float>()
740 / valid_data.len() as Float;
741 let std = variance.sqrt();
742 let min = valid_data.iter().fold(Float::INFINITY, |a, &b| a.min(b));
743 let max = valid_data
744 .iter()
745 .fold(Float::NEG_INFINITY, |a, &b| a.max(b));
746
747 StatisticalSummary {
749 mean: vec![mean],
750 std: vec![std],
751 min: vec![min],
752 max: vec![max],
753 null_count,
754 total_count,
755 }
756 }
757
758 fn create_data_snapshot(&self, data: &Array2<Float>) -> DataSnapshot {
760 let total_elements = data.len();
761 let max_samples = self.config.max_snapshot_size.min(total_elements);
762
763 let sample_indices: Vec<usize> = if total_elements <= max_samples {
764 (0..total_elements).collect()
765 } else {
766 (0..max_samples)
768 .map(|i| (i * total_elements) / max_samples)
769 .collect()
770 };
771
772 let sample_values: Vec<Vec<f64>> = sample_indices
773 .iter()
774 .map(|&idx| {
775 let (row, col) = (idx / data.ncols(), idx % data.ncols());
776 vec![data[[row, col]]]
777 })
778 .collect();
779
780 DataSnapshot {
782 sample_values,
783 sample_indices,
784 is_complete: total_elements <= max_samples,
785 }
786 }
787
788 fn measure_memory_usage(&self) -> MemoryUsage {
790 MemoryUsage {
794 component_memory: 1024 * 1024, heap_memory: 10 * 1024 * 1024, peak_memory: 15 * 1024 * 1024, allocations: 100,
798 deallocations: 90,
799 }
800 }
801
802 fn measure_cpu_usage(&self) -> f64 {
804 50.0
806 }
807
808 fn measure_io_statistics(&self) -> IoStatistics {
810 IoStatistics {
813 bytes_read: 1024,
814 bytes_written: 512,
815 read_operations: 10,
816 write_operations: 5,
817 read_latency: Duration::from_millis(1),
818 write_latency: Duration::from_millis(2),
819 }
820 }
821
822 #[must_use]
824 pub fn get_execution_trace(&self) -> &[ExecutionStep] {
825 &self.execution_trace
826 }
827
828 #[must_use]
830 pub fn get_execution_state(&self) -> &ExecutionState {
831 &self.execution_state
832 }
833
834 pub fn continue_execution(&mut self) -> SklResult<()> {
836 self.execution_state = ExecutionState::Running;
837 Ok(())
838 }
839
840 pub fn step_execution(&mut self) -> SklResult<()> {
842 self.execution_state = ExecutionState::Stepping;
843 Ok(())
844 }
845
846 #[must_use]
848 pub fn get_performance_analysis(&self) -> PerformanceAnalysis {
849 self.profiler.analyze_performance()
850 }
851
852 #[must_use]
854 pub fn get_error_analysis(&self) -> ErrorAnalysis {
855 self.error_tracker.analyze_errors()
856 }
857
858 pub fn export_debug_report(&self, format: DebugOutputFormat) -> SklResult<String> {
860 match format {
861 DebugOutputFormat::Console => self.generate_console_report(),
862 DebugOutputFormat::Json => self.generate_json_report(),
863 DebugOutputFormat::Html => self.generate_html_report(),
864 DebugOutputFormat::Graphviz => self.generate_graphviz_report(),
865 }
866 }
867
868 fn generate_console_report(&self) -> SklResult<String> {
870 let mut report = String::new();
871 report.push_str("=== Pipeline Debug Report ===\n\n");
872
873 report.push_str(&format!(
874 "Execution Steps: {}\n",
875 self.execution_trace.len()
876 ));
877 report.push_str(&format!("Breakpoints: {}\n", self.breakpoints.len()));
878 report.push_str(&format!("Errors: {}\n", self.error_tracker.errors.len()));
879
880 report.push_str("\n=== Performance Summary ===\n");
881 let analysis = self.get_performance_analysis();
882 report.push_str(&format!(
883 "Total Execution Time: {:?}\n",
884 analysis.total_execution_time
885 ));
886 report.push_str(&format!(
887 "Bottlenecks Found: {}\n",
888 analysis.bottlenecks.len()
889 ));
890
891 report.push_str("\n=== Error Summary ===\n");
892 let error_analysis = self.get_error_analysis();
893 report.push_str(&format!("Total Errors: {}\n", error_analysis.total_errors));
894 report.push_str(&format!(
895 "Resolution Rate: {:.2}%\n",
896 error_analysis.resolution_rate * 100.0
897 ));
898
899 Ok(report)
900 }
901
902 fn generate_json_report(&self) -> SklResult<String> {
904 Ok(
906 r#"{"report_type": "debug", "format": "json", "generated_at": "2024-01-01T00:00:00Z"}"#
907 .to_string(),
908 )
909 }
910
911 fn generate_html_report(&self) -> SklResult<String> {
913 let html = r#"
914<!DOCTYPE html>
915<html>
916<head>
917 <title>Pipeline Debug Report</title>
918 <style>
919 body { font-family: Arial, sans-serif; margin: 20px; }
920 .section { margin: 20px 0; padding: 10px; border: 1px solid #ccc; }
921 .error { color: red; }
922 .success { color: green; }
923 .warning { color: orange; }
924 </style>
925</head>
926<body>
927 <h1>Pipeline Debug Report</h1>
928 <div class="section">
929 <h2>Execution Summary</h2>
930 <p>Total steps executed: [STEPS]</p>
931 <p>Total execution time: [TIME]</p>
932 </div>
933 <div class="section">
934 <h2>Performance Analysis</h2>
935 <p>Bottlenecks detected: [BOTTLENECKS]</p>
936 </div>
937 <div class="section">
938 <h2>Error Analysis</h2>
939 <p>Errors encountered: [ERRORS]</p>
940 </div>
941</body>
942</html>
943 "#
944 .to_string();
945 Ok(html)
946 }
947
948 fn generate_graphviz_report(&self) -> SklResult<String> {
950 let mut dot = String::new();
951 dot.push_str("digraph PipelineDebug {\n");
952 dot.push_str(" rankdir=LR;\n");
953 dot.push_str(" node [shape=box];\n");
954
955 for (i, step) in self.execution_trace.iter().enumerate() {
956 let color = if step.error.is_some() { "red" } else { "green" };
957 dot.push_str(&format!(
958 " step{} [label=\"{}\\n{:?}\" color={}];\n",
959 i, step.component_name, step.duration, color
960 ));
961
962 if i > 0 {
963 dot.push_str(&format!(" step{} -> step{};\n", i - 1, i));
964 }
965 }
966
967 dot.push_str("}\n");
968 Ok(dot)
969 }
970}
971
972pub struct DebugSession {
974 pub session_id: String,
976 pub start_time: SystemTime,
978 pub config: DebugConfig,
980 pub state: SessionState,
982}
983
984#[derive(Clone, Debug)]
986pub enum SessionState {
987 Active,
989 Paused,
991 Completed,
993 Aborted,
995}
996
997#[derive(Clone, Debug)]
999pub struct PerformanceAnalysis {
1000 pub total_execution_time: Duration,
1002 pub average_step_time: Duration,
1004 pub bottlenecks: Vec<Bottleneck>,
1006 pub performance_score: f64,
1008 pub recommendations: Vec<String>,
1010}
1011
1012#[derive(Clone, Debug)]
1014pub struct ErrorAnalysis {
1015 pub total_errors: usize,
1017 pub error_patterns: Vec<ErrorPattern>,
1019 pub resolution_rate: f64,
1021 pub common_causes: Vec<String>,
1023 pub recommended_fixes: Vec<String>,
1025}
1026
1027impl Default for ErrorTracker {
1028 fn default() -> Self {
1029 Self::new()
1030 }
1031}
1032
1033impl ErrorTracker {
1034 #[must_use]
1035 pub fn new() -> Self {
1036 Self {
1037 errors: Vec::new(),
1038 patterns: HashMap::new(),
1039 statistics: ErrorStatistics {
1040 total_errors: 0,
1041 errors_by_type: HashMap::new(),
1042 errors_by_component: HashMap::new(),
1043 resolution_rate: 0.0,
1044 mean_resolution_time: Duration::from_secs(0),
1045 },
1046 }
1047 }
1048
1049 pub fn record_error(&mut self, component: &str, error: StepError) {
1050 let tracked_error = TrackedError {
1051 timestamp: SystemTime::now(),
1052 component: component.to_string(),
1053 error,
1054 recovery_actions: Vec::new(),
1055 resolution_status: ErrorResolutionStatus::Unresolved,
1056 };
1057
1058 self.errors.push(tracked_error);
1059 self.update_statistics();
1060 }
1061
1062 fn update_statistics(&mut self) {
1063 self.statistics.total_errors = self.errors.len();
1064
1065 self.statistics.errors_by_type.clear();
1066 self.statistics.errors_by_component.clear();
1067
1068 for error in &self.errors {
1069 *self
1070 .statistics
1071 .errors_by_type
1072 .entry(error.error.error_type.clone())
1073 .or_insert(0) += 1;
1074 *self
1075 .statistics
1076 .errors_by_component
1077 .entry(error.component.clone())
1078 .or_insert(0) += 1;
1079 }
1080
1081 let resolved_count = self
1082 .errors
1083 .iter()
1084 .filter(|e| matches!(e.resolution_status, ErrorResolutionStatus::Resolved))
1085 .count();
1086
1087 self.statistics.resolution_rate = if self.errors.is_empty() {
1088 0.0
1089 } else {
1090 resolved_count as f64 / self.errors.len() as f64
1091 };
1092 }
1093
1094 #[must_use]
1095 pub fn analyze_errors(&self) -> ErrorAnalysis {
1096 ErrorAnalysis {
1098 total_errors: self.statistics.total_errors,
1099 error_patterns: self.patterns.values().cloned().collect(),
1100 resolution_rate: self.statistics.resolution_rate,
1101 common_causes: self.get_common_causes(),
1102 recommended_fixes: self.get_recommended_fixes(),
1103 }
1104 }
1105
1106 fn get_common_causes(&self) -> Vec<String> {
1107 vec![
1109 "Data type mismatch".to_string(),
1110 "Memory allocation failure".to_string(),
1111 "Invalid parameter values".to_string(),
1112 ]
1113 }
1114
1115 fn get_recommended_fixes(&self) -> Vec<String> {
1116 vec![
1118 "Validate input data types before processing".to_string(),
1119 "Implement memory usage monitoring".to_string(),
1120 "Add parameter validation".to_string(),
1121 ]
1122 }
1123}
1124
1125impl PerformanceProfiler {
1126 #[must_use]
1127 pub fn new(config: ProfilerConfig) -> Self {
1128 Self {
1129 measurements: Vec::new(),
1130 bottleneck_detector: BottleneckDetector::new(),
1131 config,
1132 }
1133 }
1134
1135 pub fn record_measurement(&mut self, measurement: PerformanceMeasurement) {
1136 self.measurements.push(measurement);
1137 self.bottleneck_detector
1138 .analyze_measurement(self.measurements.last().unwrap());
1139 }
1140
1141 #[must_use]
1142 pub fn analyze_performance(&self) -> PerformanceAnalysis {
1143 let total_time = self.measurements.iter().map(|m| m.execution_time).sum();
1144
1145 let average_time = if self.measurements.is_empty() {
1146 Duration::from_secs(0)
1147 } else {
1148 total_time / self.measurements.len() as u32
1149 };
1150
1151 let bottlenecks = self.bottleneck_detector.get_bottlenecks();
1152 let performance_score = self.calculate_performance_score();
1153 let recommendations = self.generate_recommendations();
1154
1155 PerformanceAnalysis {
1157 total_execution_time: total_time,
1158 average_step_time: average_time,
1159 bottlenecks,
1160 performance_score,
1161 recommendations,
1162 }
1163 }
1164
1165 fn calculate_performance_score(&self) -> f64 {
1166 if self.bottleneck_detector.bottlenecks.is_empty() {
1168 90.0
1169 } else {
1170 100.0 - (self.bottleneck_detector.bottlenecks.len() as f64 * 10.0)
1171 }
1172 }
1173
1174 fn generate_recommendations(&self) -> Vec<String> {
1175 let mut recommendations = Vec::new();
1176
1177 for bottleneck in &self.bottleneck_detector.bottlenecks {
1178 recommendations.extend(bottleneck.optimizations.clone());
1179 }
1180
1181 if recommendations.is_empty() {
1182 recommendations.push("Performance looks good!".to_string());
1183 }
1184
1185 recommendations
1186 }
1187}
1188
1189impl Default for BottleneckDetector {
1190 fn default() -> Self {
1191 Self::new()
1192 }
1193}
1194
1195impl BottleneckDetector {
1196 #[must_use]
1197 pub fn new() -> Self {
1198 Self {
1199 bottlenecks: Vec::new(),
1200 thresholds: BottleneckThresholds::default(),
1201 analysis_history: Vec::new(),
1202 }
1203 }
1204
1205 pub fn analyze_measurement(&mut self, measurement: &PerformanceMeasurement) {
1206 if measurement.cpu_usage > self.thresholds.cpu_threshold {
1208 self.add_bottleneck(
1209 BottleneckType::Cpu,
1210 &measurement.component,
1211 BottleneckSeverity::High,
1212 "High CPU usage detected",
1213 );
1214 }
1215
1216 if measurement.memory_usage.component_memory > self.thresholds.memory_threshold {
1218 self.add_bottleneck(
1219 BottleneckType::Memory,
1220 &measurement.component,
1221 BottleneckSeverity::Medium,
1222 "High memory usage detected",
1223 );
1224 }
1225
1226 if measurement.execution_time > self.thresholds.time_threshold {
1228 self.add_bottleneck(
1229 BottleneckType::Algorithm,
1230 &measurement.component,
1231 BottleneckSeverity::Medium,
1232 "Slow execution detected",
1233 );
1234 }
1235 }
1236
1237 fn add_bottleneck(
1238 &mut self,
1239 bottleneck_type: BottleneckType,
1240 component: &str,
1241 severity: BottleneckSeverity,
1242 description: &str,
1243 ) {
1244 let optimizations = match bottleneck_type {
1245 BottleneckType::Cpu => vec![
1246 "Consider parallel processing".to_string(),
1247 "Optimize algorithm complexity".to_string(),
1248 ],
1249 BottleneckType::Memory => vec![
1250 "Implement data streaming".to_string(),
1251 "Use memory-efficient data structures".to_string(),
1252 ],
1253 BottleneckType::Algorithm => vec![
1254 "Profile and optimize hot paths".to_string(),
1255 "Consider algorithm alternatives".to_string(),
1256 ],
1257 _ => vec!["Investigate component implementation".to_string()],
1258 };
1259
1260 let bottleneck = Bottleneck {
1261 bottleneck_type,
1262 component: component.to_string(),
1263 severity,
1264 impact: PerformanceImpact {
1265 slowdown_factor: 1.5,
1266 time_impact: Duration::from_millis(100),
1267 memory_overhead: 1024 * 1024,
1268 throughput_impact: 0.8,
1269 },
1270 optimizations,
1271 detected_at: SystemTime::now(),
1272 };
1273
1274 self.bottlenecks.push(bottleneck);
1275 }
1276
1277 #[must_use]
1278 pub fn get_bottlenecks(&self) -> Vec<Bottleneck> {
1279 self.bottlenecks.clone()
1280 }
1281}
1282
1283impl DebugSession {
1284 #[must_use]
1285 pub fn new(config: DebugConfig) -> Self {
1286 Self {
1287 session_id: format!(
1288 "debug_{}",
1289 SystemTime::now()
1290 .duration_since(UNIX_EPOCH)
1291 .unwrap()
1292 .as_secs()
1293 ),
1294 start_time: SystemTime::now(),
1295 config,
1296 state: SessionState::Active,
1297 }
1298 }
1299
1300 pub fn pause(&mut self) {
1301 self.state = SessionState::Paused;
1302 }
1303
1304 pub fn resume(&mut self) {
1305 self.state = SessionState::Active;
1306 }
1307
1308 pub fn complete(&mut self) {
1309 self.state = SessionState::Completed;
1310 }
1311
1312 pub fn abort(&mut self) {
1313 self.state = SessionState::Aborted;
1314 }
1315}
1316
1317pub struct InteractiveDebugger {
1319 debugger: PipelineDebugger,
1320 session: Option<DebugSession>,
1321 command_history: Vec<String>,
1322}
1323
1324impl InteractiveDebugger {
1325 #[must_use]
1326 pub fn new(config: DebugConfig) -> Self {
1327 Self {
1328 debugger: PipelineDebugger::new(config),
1329 session: None,
1330 command_history: Vec::new(),
1331 }
1332 }
1333
1334 pub fn start_session(&mut self) -> SklResult<()> {
1336 let session = self.debugger.start_debug_session()?;
1337 self.session = Some(session);
1338 println!("Debug session started. Type 'help' for available commands.");
1339 Ok(())
1340 }
1341
1342 pub fn process_command(&mut self, command: &str) -> SklResult<String> {
1344 self.command_history.push(command.to_string());
1345
1346 let parts: Vec<&str> = command.split_whitespace().collect();
1347 if parts.is_empty() {
1348 return Ok("Empty command".to_string());
1349 }
1350
1351 match parts[0] {
1352 "help" => Ok(self.get_help_text()),
1353 "continue" | "c" => {
1354 self.debugger.continue_execution()?;
1355 Ok("Continuing execution...".to_string())
1356 }
1357 "step" | "s" => {
1358 self.debugger.step_execution()?;
1359 Ok("Stepping to next execution point...".to_string())
1360 }
1361 "breakpoint" | "bp" => self.handle_breakpoint_command(&parts[1..]),
1362 "trace" => Ok(self.get_execution_trace()),
1363 "performance" | "perf" => Ok(self.get_performance_summary()),
1364 "errors" => Ok(self.get_error_summary()),
1365 "export" => self.handle_export_command(&parts[1..]),
1366 "quit" | "exit" => {
1367 if let Some(ref mut session) = self.session {
1368 session.complete();
1369 }
1370 Ok("Debug session ended.".to_string())
1371 }
1372 _ => Ok(format!(
1373 "Unknown command: {}. Type 'help' for available commands.",
1374 parts[0]
1375 )),
1376 }
1377 }
1378
1379 fn get_help_text(&self) -> String {
1380 r"Available debugging commands:
1381 help - Show this help text
1382 continue, c - Continue execution
1383 step, s - Step to next execution point
1384 breakpoint, bp - Manage breakpoints (add/remove/list)
1385 trace - Show execution trace
1386 performance, perf - Show performance analysis
1387 errors - Show error analysis
1388 export - Export debug report
1389 quit, exit - End debug session"
1390 .to_string()
1391 }
1392
1393 fn handle_breakpoint_command(&mut self, args: &[&str]) -> SklResult<String> {
1394 if args.is_empty() {
1395 return Ok("Usage: breakpoint [add|remove|list] [args...]".to_string());
1396 }
1397
1398 match args[0] {
1399 "add" => {
1400 if args.len() < 2 {
1401 return Ok("Usage: breakpoint add <component_name>".to_string());
1402 }
1403 let breakpoint = Breakpoint {
1404 id: format!("bp_{}", uuid::Uuid::new_v4()),
1405 component_name: args[1].to_string(),
1406 condition: BreakpointCondition::Always,
1407 frequency: BreakpointFrequency::Always,
1408 hit_count: 0,
1409 enabled: true,
1410 };
1411 self.debugger.add_breakpoint(breakpoint);
1412 Ok(format!("Breakpoint added for component: {}", args[1]))
1413 }
1414 "list" => {
1415 let mut result = String::new();
1416 result.push_str("Active breakpoints:\n");
1417 for (id, bp) in &self.debugger.breakpoints {
1418 result.push_str(&format!(
1419 " {} - {} (hits: {}, enabled: {})\n",
1420 id, bp.component_name, bp.hit_count, bp.enabled
1421 ));
1422 }
1423 Ok(result)
1424 }
1425 "remove" => {
1426 if args.len() < 2 {
1427 return Ok("Usage: breakpoint remove <breakpoint_id>".to_string());
1428 }
1429 match self.debugger.remove_breakpoint(args[1]) {
1430 Some(_) => Ok(format!("Breakpoint {} removed", args[1])),
1431 None => Ok(format!("Breakpoint {} not found", args[1])),
1432 }
1433 }
1434 _ => Ok("Usage: breakpoint [add|remove|list] [args...]".to_string()),
1435 }
1436 }
1437
1438 fn get_execution_trace(&self) -> String {
1439 let mut result = String::new();
1440 result.push_str("Execution Trace:\n");
1441 for (i, step) in self.debugger.get_execution_trace().iter().enumerate() {
1442 result.push_str(&format!(
1443 " {}: {} ({:?}) - {}",
1444 i,
1445 step.component_name,
1446 step.duration,
1447 if step.error.is_some() { "ERROR" } else { "OK" }
1448 ));
1449 result.push('\n');
1450 }
1451 result
1452 }
1453
1454 fn get_performance_summary(&self) -> String {
1455 let analysis = self.debugger.get_performance_analysis();
1456 format!(
1457 "Performance Summary:\n Total Time: {:?}\n Average Step Time: {:?}\n Bottlenecks: {}\n Performance Score: {:.1}/100",
1458 analysis.total_execution_time,
1459 analysis.average_step_time,
1460 analysis.bottlenecks.len(),
1461 analysis.performance_score
1462 )
1463 }
1464
1465 fn get_error_summary(&self) -> String {
1466 let analysis = self.debugger.get_error_analysis();
1467 format!(
1468 "Error Summary:\n Total Errors: {}\n Resolution Rate: {:.1}%\n Error Patterns: {}",
1469 analysis.total_errors,
1470 analysis.resolution_rate * 100.0,
1471 analysis.error_patterns.len()
1472 )
1473 }
1474
1475 fn handle_export_command(&self, args: &[&str]) -> SklResult<String> {
1476 let format = if args.is_empty() {
1477 DebugOutputFormat::Console
1478 } else {
1479 match args[0] {
1480 "json" => DebugOutputFormat::Json,
1481 "html" => DebugOutputFormat::Html,
1482 "graphviz" | "dot" => DebugOutputFormat::Graphviz,
1483 _ => DebugOutputFormat::Console,
1484 }
1485 };
1486
1487 let report = self.debugger.export_debug_report(format)?;
1488 Ok(format!("Debug report exported:\n{report}"))
1489 }
1490}
1491
1492#[allow(non_snake_case)]
1493#[cfg(test)]
1494mod tests {
1495 use super::*;
1496
1497 #[test]
1498 fn test_debugger_creation() {
1499 let config = DebugConfig::default();
1500 let debugger = PipelineDebugger::new(config);
1501 assert_eq!(debugger.execution_trace.len(), 0);
1502 assert_eq!(debugger.breakpoints.len(), 0);
1503 }
1504
1505 #[test]
1506 fn test_breakpoint_management() {
1507 let mut debugger = PipelineDebugger::new(DebugConfig::default());
1508
1509 let breakpoint = Breakpoint {
1510 id: "test_bp".to_string(),
1511 component_name: "test_component".to_string(),
1512 condition: BreakpointCondition::Always,
1513 frequency: BreakpointFrequency::Always,
1514 hit_count: 0,
1515 enabled: true,
1516 };
1517
1518 debugger.add_breakpoint(breakpoint);
1519 assert_eq!(debugger.breakpoints.len(), 1);
1520
1521 let removed = debugger.remove_breakpoint("test_bp");
1522 assert!(removed.is_some());
1523 assert_eq!(debugger.breakpoints.len(), 0);
1524 }
1525
1526 #[test]
1527 fn test_execution_recording() {
1528 let mut debugger = PipelineDebugger::new(DebugConfig::default());
1529
1530 let result = debugger.record_step(
1531 "step1",
1532 "test_component",
1533 Duration::from_millis(100),
1534 None,
1535 None,
1536 None,
1537 );
1538
1539 assert!(result.is_ok());
1540 assert_eq!(debugger.execution_trace.len(), 1);
1541 assert_eq!(debugger.execution_trace[0].step_id, "step1");
1542 assert_eq!(debugger.execution_trace[0].component_name, "test_component");
1543 }
1544
1545 #[test]
1546 fn test_data_summary_creation() {
1547 let debugger = PipelineDebugger::new(DebugConfig::default());
1548 let data = Array2::<Float>::ones((10, 5));
1549 let summary = debugger.create_data_summary(&data);
1550
1551 assert_eq!(summary.shape, vec![10, 5]);
1552 assert_eq!(summary.data_type, "Array2<Float>");
1553 assert!(summary.statistics.is_some());
1554 }
1555
1556 #[test]
1557 fn test_performance_measurement() {
1558 let mut profiler = PerformanceProfiler::new(ProfilerConfig::default());
1559
1560 let measurement = PerformanceMeasurement {
1561 component: "test".to_string(),
1562 execution_time: Duration::from_millis(100),
1563 cpu_usage: 50.0,
1564 memory_usage: MemoryUsage {
1565 component_memory: 1024,
1566 heap_memory: 2048,
1567 peak_memory: 3072,
1568 allocations: 10,
1569 deallocations: 8,
1570 },
1571 io_operations: IoStatistics {
1572 bytes_read: 1024,
1573 bytes_written: 512,
1574 read_operations: 5,
1575 write_operations: 3,
1576 read_latency: Duration::from_millis(1),
1577 write_latency: Duration::from_millis(2),
1578 },
1579 custom_metrics: HashMap::new(),
1580 };
1581
1582 profiler.record_measurement(measurement);
1583 assert_eq!(profiler.measurements.len(), 1);
1584 }
1585
1586 #[test]
1587 fn test_error_tracking() {
1588 let mut tracker = ErrorTracker::new();
1589
1590 let error = StepError {
1591 error_type: "TestError".to_string(),
1592 message: "Test error message".to_string(),
1593 stack_trace: None,
1594 context: HashMap::new(),
1595 suggested_fixes: vec!["Fix suggestion".to_string()],
1596 };
1597
1598 tracker.record_error("test_component", error);
1599 assert_eq!(tracker.errors.len(), 1);
1600 assert_eq!(tracker.statistics.total_errors, 1);
1601 }
1602
1603 #[test]
1604 fn test_interactive_debugger() {
1605 let mut debugger = InteractiveDebugger::new(DebugConfig::default());
1606
1607 let result = debugger.process_command("help");
1608 assert!(result.is_ok());
1609
1610 let result = debugger.process_command("breakpoint list");
1611 assert!(result.is_ok());
1612
1613 let result = debugger.process_command("trace");
1614 assert!(result.is_ok());
1615 }
1616
1617 #[test]
1618 fn test_debug_session() {
1619 let config = DebugConfig::default();
1620 let mut session = DebugSession::new(config);
1621
1622 assert!(matches!(session.state, SessionState::Active));
1623
1624 session.pause();
1625 assert!(matches!(session.state, SessionState::Paused));
1626
1627 session.resume();
1628 assert!(matches!(session.state, SessionState::Active));
1629
1630 session.complete();
1631 assert!(matches!(session.state, SessionState::Completed));
1632 }
1633
1634 #[test]
1635 fn test_bottleneck_detection() {
1636 let mut detector = BottleneckDetector::new();
1637
1638 let measurement = PerformanceMeasurement {
1639 component: "slow_component".to_string(),
1640 execution_time: Duration::from_secs(20), cpu_usage: 90.0, memory_usage: MemoryUsage {
1643 component_memory: 2 * 1024 * 1024 * 1024, heap_memory: 4 * 1024 * 1024 * 1024,
1645 peak_memory: 5 * 1024 * 1024 * 1024,
1646 allocations: 1000,
1647 deallocations: 800,
1648 },
1649 io_operations: IoStatistics {
1650 bytes_read: 1024 * 1024,
1651 bytes_written: 512 * 1024,
1652 read_operations: 100,
1653 write_operations: 50,
1654 read_latency: Duration::from_millis(200), write_latency: Duration::from_millis(300),
1656 },
1657 custom_metrics: HashMap::new(),
1658 };
1659
1660 detector.analyze_measurement(&measurement);
1661 let bottlenecks = detector.get_bottlenecks();
1662
1663 assert!(bottlenecks.len() > 0);
1665 }
1666
1667 #[test]
1668 fn test_report_generation() {
1669 let debugger = PipelineDebugger::new(DebugConfig::default());
1670
1671 let console_report = debugger.generate_console_report();
1672 assert!(console_report.is_ok());
1673
1674 let json_report = debugger.generate_json_report();
1675 assert!(json_report.is_ok());
1676
1677 let html_report = debugger.generate_html_report();
1678 assert!(html_report.is_ok());
1679
1680 let graphviz_report = debugger.generate_graphviz_report();
1681 assert!(graphviz_report.is_ok());
1682 }
1683}