1#![allow(dead_code)]
7
8#[cfg(feature = "dwave")]
9use crate::compile::{Compile, CompiledModel};
10#[cfg(feature = "plotters")]
11use plotters::prelude::*;
12use serde::{Deserialize, Serialize};
13use std::collections::{BTreeMap, HashMap};
14use std::sync::{Arc, Mutex};
15use std::thread;
16use std::time::{Duration, Instant};
17
18pub struct PerformanceProfiler {
20 config: ProfilerConfig,
22 profiles: Vec<Profile>,
24 current_profile: Option<ProfileContext>,
26 collectors: Vec<Box<dyn MetricsCollector>>,
28 analyzer: PerformanceAnalyzer,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ProfilerConfig {
34 pub enabled: bool,
36 pub sampling_interval: Duration,
38 pub metrics: Vec<MetricType>,
40 pub profile_memory: bool,
42 pub profile_cpu: bool,
44 pub profile_gpu: bool,
46 pub detailed_timing: bool,
48 pub output_format: OutputFormat,
50 pub auto_save_interval: Option<Duration>,
52}
53
54impl Default for ProfilerConfig {
55 fn default() -> Self {
56 Self {
57 enabled: true,
58 sampling_interval: Duration::from_millis(100),
59 metrics: vec![MetricType::Time, MetricType::Memory],
60 profile_memory: true,
61 profile_cpu: true,
62 profile_gpu: false,
63 detailed_timing: false,
64 output_format: OutputFormat::Json,
65 auto_save_interval: Some(Duration::from_secs(60)),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
71pub enum MetricType {
72 Time,
74 Memory,
76 CPU,
78 GPU,
80 Cache,
82 IO,
84 Network,
86 Custom(String),
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub enum OutputFormat {
92 Json,
94 Binary,
96 Csv,
98 FlameGraph,
100 ChromeTrace,
102}
103
104#[derive(Debug, Clone)]
106pub struct Profile {
107 pub id: String,
109 pub start_time: Instant,
111 pub end_time: Option<Instant>,
113 pub events: Vec<ProfileEvent>,
115 pub metrics: MetricsData,
117 pub call_graph: CallGraph,
119 pub resource_usage: ResourceUsage,
121}
122
123#[derive(Debug, Clone)]
124pub struct ProfileEvent {
125 pub timestamp: Instant,
127 pub event_type: EventType,
129 pub name: String,
131 pub duration: Option<Duration>,
133 pub data: HashMap<String, String>,
135 pub thread_id: thread::ThreadId,
137}
138
139#[derive(Debug, Clone)]
140pub enum EventType {
141 FunctionCall,
143 FunctionReturn,
145 MemoryAlloc,
147 MemoryFree,
149 IOOperation,
151 Synchronization,
153 Custom(String),
155}
156
157#[derive(Debug, Clone)]
158pub struct MetricsData {
159 pub time_metrics: TimeMetrics,
161 pub memory_metrics: MemoryMetrics,
163 pub computation_metrics: ComputationMetrics,
165 pub quality_metrics: QualityMetrics,
167}
168
169#[derive(Debug, Clone)]
170pub struct TimeMetrics {
171 pub total_time: Duration,
173 pub qubo_generation_time: Duration,
175 pub compilation_time: Duration,
177 pub solving_time: Duration,
179 pub post_processing_time: Duration,
181 pub function_times: BTreeMap<String, Duration>,
183 pub percentiles: Percentiles,
185}
186
187#[derive(Debug, Clone)]
188pub struct Percentiles {
189 pub p50: Duration,
190 pub p90: Duration,
191 pub p95: Duration,
192 pub p99: Duration,
193 pub p999: Duration,
194}
195
196#[derive(Debug, Clone)]
197pub struct MemoryMetrics {
198 pub peak_memory: usize,
200 pub avg_memory: usize,
202 pub allocations: usize,
204 pub deallocations: usize,
206 pub largest_allocation: usize,
208 pub memory_timeline: Vec<(Instant, usize)>,
210}
211
212#[derive(Debug, Clone)]
213pub struct ComputationMetrics {
214 pub flops: f64,
216 pub memory_bandwidth: f64,
218 pub cache_hit_rate: f64,
220 pub branch_prediction_accuracy: f64,
222 pub vectorization_efficiency: f64,
224}
225
226#[derive(Debug, Clone)]
227pub struct QualityMetrics {
228 pub quality_timeline: Vec<(Duration, f64)>,
230 pub convergence_rate: f64,
232 pub improvement_per_iteration: f64,
234 pub time_to_first_solution: Duration,
236 pub time_to_best_solution: Duration,
238}
239
240#[derive(Debug, Clone)]
241pub struct CallGraph {
242 pub nodes: Vec<CallNode>,
244 pub edges: Vec<CallEdge>,
246 pub roots: Vec<usize>,
248}
249
250#[derive(Debug, Clone)]
251pub struct CallNode {
252 pub id: usize,
254 pub name: String,
256 pub total_time: Duration,
258 pub self_time: Duration,
260 pub call_count: usize,
262 pub avg_time: Duration,
264}
265
266#[derive(Debug, Clone)]
267pub struct CallEdge {
268 pub from: usize,
270 pub to: usize,
272 pub call_count: usize,
274 pub total_time: Duration,
276}
277
278#[derive(Debug, Clone)]
279pub struct ResourceUsage {
280 pub cpu_usage: Vec<(Instant, f64)>,
282 pub memory_usage: Vec<(Instant, usize)>,
284 pub gpu_usage: Vec<(Instant, f64)>,
286 pub io_operations: Vec<IOOperation>,
288 pub network_operations: Vec<NetworkOperation>,
290}
291
292#[derive(Debug, Clone)]
293pub struct IOOperation {
294 pub timestamp: Instant,
296 pub operation: IOOpType,
298 pub bytes: usize,
300 pub duration: Duration,
302 pub target: String,
304}
305
306#[derive(Debug, Clone)]
307pub enum IOOpType {
308 Read,
309 Write,
310 Seek,
311 Flush,
312}
313
314#[derive(Debug, Clone)]
315pub struct NetworkOperation {
316 pub timestamp: Instant,
318 pub operation: NetworkOpType,
320 pub bytes: usize,
322 pub duration: Duration,
324 pub endpoint: String,
326}
327
328#[derive(Debug, Clone)]
329pub enum NetworkOpType {
330 Send,
331 Receive,
332 Connect,
333 Disconnect,
334}
335
336#[derive(Debug)]
338struct ProfileContext {
339 profile: Profile,
341 call_stack: Vec<(String, Instant)>,
343 timers: HashMap<String, Instant>,
345 metrics_buffer: MetricsBuffer,
347}
348
349#[derive(Debug, Default)]
350struct MetricsBuffer {
351 time_samples: Vec<(String, Duration)>,
353 memory_samples: Vec<(Instant, usize)>,
355 cpu_samples: Vec<(Instant, f64)>,
357 custom_metrics: HashMap<String, Vec<f64>>,
359}
360
361pub trait MetricsCollector: Send + Sync {
363 fn collect(&self) -> Result<MetricsSample, String>;
365
366 fn name(&self) -> &str;
368
369 fn supported_metrics(&self) -> Vec<MetricType>;
371}
372
373#[derive(Debug, Clone)]
374pub struct MetricsSample {
375 pub timestamp: Instant,
377 pub values: HashMap<MetricType, f64>,
379}
380
381pub struct PerformanceAnalyzer {
383 config: AnalysisConfig,
385 bottleneck_detector: BottleneckDetector,
387 optimization_suggester: OptimizationSuggester,
389}
390
391#[derive(Debug, Clone)]
392pub struct AnalysisConfig {
393 pub detect_bottlenecks: bool,
395 pub suggest_optimizations: bool,
397 pub detect_anomalies: bool,
399 pub detect_regressions: bool,
401 pub baseline: Option<Profile>,
403}
404
405pub struct BottleneckDetector {
407 hot_function_threshold: f64,
409 detect_memory_leaks: bool,
411 detect_contention: bool,
413}
414
415pub struct OptimizationSuggester {
417 rules: Vec<OptimizationRule>,
419 history: Vec<Profile>,
421}
422
423#[derive(Debug, Clone)]
424pub struct OptimizationRule {
425 pub name: String,
427 pub condition: RuleCondition,
429 pub suggestion: String,
431 pub improvement: f64,
433}
434
435#[derive(Debug, Clone)]
436pub enum RuleCondition {
437 HighFunctionTime {
439 function: String,
440 threshold: Duration,
441 },
442 HighMemoryUsage { threshold: usize },
444 LowCacheHitRate { threshold: f64 },
446 Custom(String),
448}
449
450impl PerformanceProfiler {
451 pub fn start_real_time_monitoring(&mut self) -> Result<RealTimeMonitor, String> {
453 if !self.config.enabled {
454 return Err("Profiling not enabled".to_string());
455 }
456
457 let monitor = RealTimeMonitor::new(
458 self.config.sampling_interval,
459 self.collectors
460 .iter()
461 .map(|c| c.name().to_string())
462 .collect(),
463 )?;
464
465 Ok(monitor)
466 }
467
468 pub fn predict_performance(
470 &self,
471 problem_characteristics: &ProblemCharacteristics,
472 ) -> PerformancePrediction {
473 let predictor = PerformancePredictor::new(&self.profiles);
474 predictor.predict(problem_characteristics)
475 }
476
477 pub fn generate_recommendations(&self, profile: &Profile) -> Vec<OptimizationRecommendation> {
479 let mut recommendations = Vec::new();
480
481 let analysis = self.analyze_profile(profile);
483
484 for bottleneck in &analysis.bottlenecks {
485 match bottleneck.bottleneck_type {
486 BottleneckType::CPU => {
487 if bottleneck.impact > 0.3 {
488 recommendations.push(OptimizationRecommendation {
489 title: format!("Optimize hot function: {}", bottleneck.location),
490 description:
491 "Consider algorithmic improvements, caching, or parallelization"
492 .to_string(),
493 category: RecommendationCategory::Algorithm,
494 impact: RecommendationImpact::High,
495 effort: ImplementationEffort::Medium,
496 estimated_improvement: bottleneck.impact * 0.5,
497 code_suggestions: vec![
498 "Add memoization for expensive calculations".to_string(),
499 "Consider parallel processing".to_string(),
500 "Profile inner loops for micro-optimizations".to_string(),
501 ],
502 });
503 }
504 }
505 BottleneckType::Memory => {
506 recommendations.push(OptimizationRecommendation {
507 title: "Memory usage optimization".to_string(),
508 description: "Reduce memory allocations and improve data locality"
509 .to_string(),
510 category: RecommendationCategory::Memory,
511 impact: RecommendationImpact::Medium,
512 effort: ImplementationEffort::Low,
513 estimated_improvement: 0.2,
514 code_suggestions: vec![
515 "Use object pooling for frequently allocated objects".to_string(),
516 "Consider more compact data structures".to_string(),
517 "Implement streaming for large datasets".to_string(),
518 ],
519 });
520 }
521 _ => {}
522 }
523 }
524
525 if profile.metrics.computation_metrics.cache_hit_rate < 0.8 {
527 recommendations.push(OptimizationRecommendation {
528 title: "Improve cache locality".to_string(),
529 description: "Restructure data access patterns for better cache performance"
530 .to_string(),
531 category: RecommendationCategory::Memory,
532 impact: RecommendationImpact::Medium,
533 effort: ImplementationEffort::High,
534 estimated_improvement: 0.15,
535 code_suggestions: vec![
536 "Use structure-of-arrays instead of array-of-structures".to_string(),
537 "Implement cache-oblivious algorithms".to_string(),
538 "Add data prefetching hints".to_string(),
539 ],
540 });
541 }
542
543 recommendations.sort_by(|a, b| {
545 b.estimated_improvement
546 .partial_cmp(&a.estimated_improvement)
547 .expect("Failed to compare estimated improvements in recommendation sorting")
548 });
549
550 recommendations
551 }
552
553 pub fn export_for_external_tool(
555 &self,
556 profile: &Profile,
557 tool: ExternalTool,
558 ) -> Result<String, String> {
559 match tool {
560 ExternalTool::Perf => Self::export_perf_script(profile),
561 ExternalTool::Valgrind => Self::export_valgrind_format(profile),
562 ExternalTool::FlameScope => Self::export_flamescope_format(profile),
563 ExternalTool::SpeedScope => Self::export_speedscope_format(profile),
564 }
565 }
566
567 fn export_perf_script(profile: &Profile) -> Result<String, String> {
569 let mut output = String::new();
570
571 for event in &profile.events {
572 if matches!(event.event_type, EventType::FunctionCall) {
573 output.push_str(&format!(
574 "{} {} [{}] {}: {}\n",
575 "comm",
576 std::process::id(),
577 format!("{:?}", event.thread_id),
578 event.timestamp.elapsed().as_micros(),
579 event.name
580 ));
581 }
582 }
583
584 Ok(output)
585 }
586
587 fn export_valgrind_format(profile: &Profile) -> Result<String, String> {
589 let mut output = String::new();
590
591 output.push_str("events: Instructions\n");
592 output.push_str("summary: 1000000\n\n");
593
594 for node in &profile.call_graph.nodes {
595 output.push_str(&format!(
596 "fl={}\nfn={}\n1 {}\n\n",
597 "unknown",
598 node.name,
599 node.total_time.as_micros()
600 ));
601 }
602
603 Ok(output)
604 }
605
606 fn export_flamescope_format(profile: &Profile) -> Result<String, String> {
608 let mut stacks = Vec::new();
610
611 for event in &profile.events {
612 if matches!(event.event_type, EventType::FunctionCall) {
613 if let Some(duration) = event.duration {
614 stacks.push(serde_json::json!({
615 "name": event.name,
616 "value": duration.as_micros(),
617 "start": event.timestamp.elapsed().as_micros()
618 }));
619 }
620 }
621 }
622
623 serde_json::to_string(&stacks).map_err(|e| format!("JSON error: {e}"))
624 }
625
626 fn export_speedscope_format(profile: &Profile) -> Result<String, String> {
628 let speedscope_profile = serde_json::json!({
629 "$schema": "https://www.speedscope.app/file-format-schema.json",
630 "version": "0.0.1",
631 "shared": {
632 "frames": profile.call_graph.nodes.iter().map(|node| {
633 serde_json::json!({
634 "name": node.name,
635 "file": "unknown",
636 "line": 0,
637 "col": 0
638 })
639 }).collect::<Vec<_>>()
640 },
641 "profiles": [{
642 "type": "evented",
643 "name": profile.id,
644 "unit": "microseconds",
645 "startValue": 0,
646 "endValue": profile.metrics.time_metrics.total_time.as_micros(),
647 "events": profile.events.iter().filter_map(|event| {
648 match event.event_type {
649 EventType::FunctionCall => Some(serde_json::json!({
650 "type": "O",
651 "at": event.timestamp.elapsed().as_micros(),
652 "frame": event.name
653 })),
654 EventType::FunctionReturn => Some(serde_json::json!({
655 "type": "C",
656 "at": event.timestamp.elapsed().as_micros(),
657 "frame": event.name
658 })),
659 _ => None
660 }
661 }).collect::<Vec<_>>()
662 }]
663 });
664
665 serde_json::to_string(&speedscope_profile).map_err(|e| format!("JSON error: {e}"))
666 }
667
668 pub fn start_continuous_profiling(
670 &mut self,
671 duration: Duration,
672 ) -> Result<ContinuousProfiler, String> {
673 if !self.config.enabled {
674 return Err("Profiling not enabled".to_string());
675 }
676
677 let profiler = ContinuousProfiler::new(duration, self.config.sampling_interval);
678 Ok(profiler)
679 }
680
681 pub fn benchmark_compare(&self, profiles: &[Profile]) -> BenchmarkComparison {
683 let mut comparison = BenchmarkComparison {
684 profiles: profiles.iter().map(|p| p.id.clone()).collect(),
685 metrics_comparison: Vec::new(),
686 regression_analysis: Vec::new(),
687 performance_trends: Vec::new(),
688 };
689
690 if profiles.len() < 2 {
691 return comparison;
692 }
693
694 let times: Vec<f64> = profiles
696 .iter()
697 .map(|p| p.metrics.time_metrics.total_time.as_secs_f64())
698 .collect();
699
700 comparison.metrics_comparison.push(MetricComparison {
701 metric_name: "total_time".to_string(),
702 values: times.clone(),
703 trend: if times.len() >= 2 {
704 if times[times.len() - 1] < times[0] {
705 PerformanceTrend::Improving
706 } else if times[times.len() - 1] > times[0] * 1.1 {
707 PerformanceTrend::Degrading
708 } else {
709 PerformanceTrend::Stable
710 }
711 } else {
712 PerformanceTrend::Unknown
713 },
714 variance: Self::calculate_variance(×),
715 });
716
717 let memory: Vec<f64> = profiles
719 .iter()
720 .map(|p| p.metrics.memory_metrics.peak_memory as f64)
721 .collect();
722
723 comparison.metrics_comparison.push(MetricComparison {
724 metric_name: "peak_memory".to_string(),
725 values: memory.clone(),
726 trend: if memory.len() >= 2 {
727 if memory[memory.len() - 1] < memory[0] {
728 PerformanceTrend::Improving
729 } else if memory[memory.len() - 1] > memory[0] * 1.1 {
730 PerformanceTrend::Degrading
731 } else {
732 PerformanceTrend::Stable
733 }
734 } else {
735 PerformanceTrend::Unknown
736 },
737 variance: Self::calculate_variance(&memory),
738 });
739
740 comparison
741 }
742
743 fn calculate_variance(values: &[f64]) -> f64 {
745 if values.len() < 2 {
746 return 0.0;
747 }
748
749 let mean = values.iter().sum::<f64>() / values.len() as f64;
750 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
751
752 variance.sqrt()
753 }
754 pub fn new(config: ProfilerConfig) -> Self {
756 Self {
757 config,
758 profiles: Vec::new(),
759 current_profile: None,
760 collectors: Self::default_collectors(),
761 analyzer: PerformanceAnalyzer::new(AnalysisConfig {
762 detect_bottlenecks: true,
763 suggest_optimizations: true,
764 detect_anomalies: true,
765 detect_regressions: false,
766 baseline: None,
767 }),
768 }
769 }
770
771 fn default_collectors() -> Vec<Box<dyn MetricsCollector>> {
773 vec![
774 Box::new(TimeCollector),
775 Box::new(MemoryCollector),
776 Box::new(CPUCollector),
777 ]
778 }
779
780 pub fn start_profile(&mut self, name: &str) -> Result<(), String> {
782 if !self.config.enabled {
783 return Ok(());
784 }
785
786 if self.current_profile.is_some() {
787 return Err("Profile already in progress".to_string());
788 }
789
790 let profile = Profile {
791 id: format!(
792 "{}_{}",
793 name,
794 std::time::SystemTime::now()
795 .duration_since(std::time::UNIX_EPOCH)
796 .expect("Failed to get current system time for profile ID generation")
797 .as_secs()
798 ),
799 start_time: Instant::now(),
800 end_time: None,
801 events: Vec::new(),
802 metrics: MetricsData {
803 time_metrics: TimeMetrics {
804 total_time: Duration::from_secs(0),
805 qubo_generation_time: Duration::from_secs(0),
806 compilation_time: Duration::from_secs(0),
807 solving_time: Duration::from_secs(0),
808 post_processing_time: Duration::from_secs(0),
809 function_times: BTreeMap::new(),
810 percentiles: Percentiles {
811 p50: Duration::from_secs(0),
812 p90: Duration::from_secs(0),
813 p95: Duration::from_secs(0),
814 p99: Duration::from_secs(0),
815 p999: Duration::from_secs(0),
816 },
817 },
818 memory_metrics: MemoryMetrics {
819 peak_memory: 0,
820 avg_memory: 0,
821 allocations: 0,
822 deallocations: 0,
823 largest_allocation: 0,
824 memory_timeline: Vec::new(),
825 },
826 computation_metrics: ComputationMetrics {
827 flops: 0.0,
828 memory_bandwidth: 0.0,
829 cache_hit_rate: 0.0,
830 branch_prediction_accuracy: 0.0,
831 vectorization_efficiency: 0.0,
832 },
833 quality_metrics: QualityMetrics {
834 quality_timeline: Vec::new(),
835 convergence_rate: 0.0,
836 improvement_per_iteration: 0.0,
837 time_to_first_solution: Duration::from_secs(0),
838 time_to_best_solution: Duration::from_secs(0),
839 },
840 },
841 call_graph: CallGraph {
842 nodes: Vec::new(),
843 edges: Vec::new(),
844 roots: Vec::new(),
845 },
846 resource_usage: ResourceUsage {
847 cpu_usage: Vec::new(),
848 memory_usage: Vec::new(),
849 gpu_usage: Vec::new(),
850 io_operations: Vec::new(),
851 network_operations: Vec::new(),
852 },
853 };
854
855 self.current_profile = Some(ProfileContext {
856 profile,
857 call_stack: Vec::new(),
858 timers: HashMap::new(),
859 metrics_buffer: MetricsBuffer::default(),
860 });
861
862 if self.config.sampling_interval > Duration::from_secs(0) {
864 Self::start_metrics_collection()?;
865 }
866
867 Ok(())
868 }
869
870 pub fn stop_profile(&mut self) -> Result<Profile, String> {
872 if !self.config.enabled {
873 return Err("Profiling not enabled".to_string());
874 }
875
876 let mut context = self
877 .current_profile
878 .take()
879 .ok_or("No profile in progress")?;
880
881 context.profile.end_time = Some(Instant::now());
882
883 context.profile.metrics.time_metrics.total_time = context
885 .profile
886 .end_time
887 .expect("Profile end_time should be set before calculating total time")
888 - context.profile.start_time;
889
890 Self::process_metrics_buffer(&mut context)?;
892
893 Self::build_call_graph(&mut context.profile)?;
895
896 Self::calculate_percentiles(&mut context.profile)?;
898
899 self.profiles.push(context.profile.clone());
901
902 Ok(context.profile)
903 }
904
905 pub fn enter_function(&mut self, name: &str) -> FunctionGuard {
907 if !self.config.enabled {
908 return FunctionGuard {
909 profiler: None,
910 name: String::new(),
911 };
912 }
913
914 if let Some(ref mut context) = self.current_profile {
915 let event = ProfileEvent {
916 timestamp: Instant::now(),
917 event_type: EventType::FunctionCall,
918 name: name.to_string(),
919 duration: None,
920 data: HashMap::new(),
921 thread_id: thread::current().id(),
922 };
923
924 context.profile.events.push(event);
925 context.call_stack.push((name.to_string(), Instant::now()));
926 }
927
928 FunctionGuard {
929 profiler: Some(std::ptr::from_mut::<Self>(self)),
930 name: name.to_string(),
931 }
932 }
933
934 fn exit_function(&mut self, name: &str) {
936 if let Some(ref mut context) = self.current_profile {
937 if let Some((_, enter_time)) = context.call_stack.pop() {
938 let duration = enter_time.elapsed();
939
940 let event = ProfileEvent {
941 timestamp: Instant::now(),
942 event_type: EventType::FunctionReturn,
943 name: name.to_string(),
944 duration: Some(duration),
945 data: HashMap::new(),
946 thread_id: thread::current().id(),
947 };
948
949 context.profile.events.push(event);
950
951 *context
953 .profile
954 .metrics
955 .time_metrics
956 .function_times
957 .entry(name.to_string())
958 .or_insert(Duration::from_secs(0)) += duration;
959 }
960 }
961 }
962
963 pub fn start_timer(&mut self, name: &str) {
965 if !self.config.enabled {
966 return;
967 }
968
969 if let Some(ref mut context) = self.current_profile {
970 context.timers.insert(name.to_string(), Instant::now());
971 }
972 }
973
974 pub fn stop_timer(&mut self, name: &str) -> Option<Duration> {
976 if !self.config.enabled {
977 return None;
978 }
979
980 if let Some(ref mut context) = self.current_profile {
981 if let Some(start_time) = context.timers.remove(name) {
982 let duration = start_time.elapsed();
983
984 match name {
986 "qubo_generation" => {
987 context.profile.metrics.time_metrics.qubo_generation_time = duration;
988 }
989 "compilation" => {
990 context.profile.metrics.time_metrics.compilation_time = duration;
991 }
992 "solving" => {
993 context.profile.metrics.time_metrics.solving_time = duration;
994 }
995 "post_processing" => {
996 context.profile.metrics.time_metrics.post_processing_time = duration;
997 }
998 _ => {
999 context
1000 .metrics_buffer
1001 .time_samples
1002 .push((name.to_string(), duration));
1003 }
1004 }
1005
1006 return Some(duration);
1007 }
1008 }
1009
1010 None
1011 }
1012
1013 pub fn record_allocation(&mut self, size: usize) {
1015 if !self.config.enabled || !self.config.profile_memory {
1016 return;
1017 }
1018
1019 if let Some(ref mut context) = self.current_profile {
1020 context.profile.metrics.memory_metrics.allocations += 1;
1021 context.profile.metrics.memory_metrics.largest_allocation = context
1022 .profile
1023 .metrics
1024 .memory_metrics
1025 .largest_allocation
1026 .max(size);
1027
1028 let event = ProfileEvent {
1029 timestamp: Instant::now(),
1030 event_type: EventType::MemoryAlloc,
1031 name: "allocation".to_string(),
1032 duration: None,
1033 data: {
1034 let mut data = HashMap::new();
1035 data.insert("size".to_string(), size.to_string());
1036 data
1037 },
1038 thread_id: thread::current().id(),
1039 };
1040
1041 context.profile.events.push(event);
1042 }
1043 }
1044
1045 pub fn record_deallocation(&mut self, size: usize) {
1047 if !self.config.enabled || !self.config.profile_memory {
1048 return;
1049 }
1050
1051 if let Some(ref mut context) = self.current_profile {
1052 context.profile.metrics.memory_metrics.deallocations += 1;
1053
1054 let event = ProfileEvent {
1055 timestamp: Instant::now(),
1056 event_type: EventType::MemoryFree,
1057 name: "deallocation".to_string(),
1058 duration: None,
1059 data: {
1060 let mut data = HashMap::new();
1061 data.insert("size".to_string(), size.to_string());
1062 data
1063 },
1064 thread_id: thread::current().id(),
1065 };
1066
1067 context.profile.events.push(event);
1068 }
1069 }
1070
1071 pub fn record_solution_quality(&mut self, quality: f64) {
1073 if !self.config.enabled {
1074 return;
1075 }
1076
1077 if let Some(ref mut context) = self.current_profile {
1078 let elapsed = context.profile.start_time.elapsed();
1079 context
1080 .profile
1081 .metrics
1082 .quality_metrics
1083 .quality_timeline
1084 .push((elapsed, quality));
1085
1086 if context
1088 .profile
1089 .metrics
1090 .quality_metrics
1091 .quality_timeline
1092 .len()
1093 == 1
1094 {
1095 context
1096 .profile
1097 .metrics
1098 .quality_metrics
1099 .time_to_first_solution = elapsed;
1100 }
1101 }
1102 }
1103
1104 const fn start_metrics_collection() -> Result<(), String> {
1106 Ok(())
1109 }
1110
1111 fn process_metrics_buffer(context: &mut ProfileContext) -> Result<(), String> {
1113 if !context.metrics_buffer.memory_samples.is_empty() {
1115 let total_memory: usize = context
1116 .metrics_buffer
1117 .memory_samples
1118 .iter()
1119 .map(|(_, mem)| mem)
1120 .sum();
1121 context.profile.metrics.memory_metrics.avg_memory =
1122 total_memory / context.metrics_buffer.memory_samples.len();
1123
1124 context.profile.metrics.memory_metrics.peak_memory = context
1125 .metrics_buffer
1126 .memory_samples
1127 .iter()
1128 .map(|(_, mem)| *mem)
1129 .max()
1130 .unwrap_or(0);
1131
1132 context.profile.metrics.memory_metrics.memory_timeline =
1133 context.metrics_buffer.memory_samples.clone();
1134 }
1135
1136 if !context.metrics_buffer.cpu_samples.is_empty() {
1138 context.profile.resource_usage.cpu_usage = context.metrics_buffer.cpu_samples.clone();
1139 }
1140
1141 Ok(())
1142 }
1143
1144 fn build_call_graph(profile: &mut Profile) -> Result<(), String> {
1146 let mut node_map: HashMap<String, usize> = HashMap::new();
1147 let mut nodes = Vec::new();
1148 let mut edges: HashMap<(usize, usize), CallEdge> = HashMap::new();
1149
1150 for (func_name, &total_time) in &profile.metrics.time_metrics.function_times {
1152 let node_id = nodes.len();
1153 node_map.insert(func_name.clone(), node_id);
1154
1155 nodes.push(CallNode {
1156 id: node_id,
1157 name: func_name.clone(),
1158 total_time,
1159 self_time: total_time, call_count: 0, avg_time: Duration::from_secs(0), });
1163 }
1164
1165 let mut call_stack: Vec<usize> = Vec::new();
1167
1168 for event in &profile.events {
1169 match event.event_type {
1170 EventType::FunctionCall => {
1171 if let Some(&node_id) = node_map.get(&event.name) {
1172 nodes[node_id].call_count += 1;
1173
1174 if let Some(&parent_id) = call_stack.last() {
1175 let edge_key = (parent_id, node_id);
1176 edges
1177 .entry(edge_key)
1178 .and_modify(|e| e.call_count += 1)
1179 .or_insert(CallEdge {
1180 from: parent_id,
1181 to: node_id,
1182 call_count: 1,
1183 total_time: Duration::from_secs(0),
1184 });
1185 }
1186
1187 call_stack.push(node_id);
1188 }
1189 }
1190 EventType::FunctionReturn => {
1191 call_stack.pop();
1192 }
1193 _ => {}
1194 }
1195 }
1196
1197 for node in &mut nodes {
1199 if node.call_count > 0 {
1200 node.avg_time = node.total_time / node.call_count as u32;
1201 }
1202 }
1203
1204 let mut has_parent = vec![false; nodes.len()];
1206 for edge in edges.values() {
1207 has_parent[edge.to] = true;
1208 }
1209
1210 let roots: Vec<usize> = (0..nodes.len()).filter(|&i| !has_parent[i]).collect();
1211
1212 profile.call_graph = CallGraph {
1213 nodes,
1214 edges: edges.into_values().collect(),
1215 roots,
1216 };
1217
1218 Ok(())
1219 }
1220
1221 fn calculate_percentiles(profile: &mut Profile) -> Result<(), String> {
1223 let mut durations: Vec<Duration> =
1224 profile.events.iter().filter_map(|e| e.duration).collect();
1225
1226 if durations.is_empty() {
1227 return Ok(());
1228 }
1229
1230 durations.sort();
1231
1232 let len = durations.len();
1233 profile.metrics.time_metrics.percentiles = Percentiles {
1234 p50: durations[len * 50 / 100],
1235 p90: durations[len * 90 / 100],
1236 p95: durations[len * 95 / 100],
1237 p99: durations[len * 99 / 100],
1238 p999: durations[len.saturating_sub(1)],
1239 };
1240
1241 Ok(())
1242 }
1243
1244 pub fn analyze_profile(&self, profile: &Profile) -> AnalysisReport {
1246 self.analyzer.analyze(profile)
1247 }
1248
1249 pub fn compare_profiles(&self, profile1: &Profile, profile2: &Profile) -> ComparisonReport {
1251 ComparisonReport {
1252 time_comparison: Self::compare_time_metrics(
1253 &profile1.metrics.time_metrics,
1254 &profile2.metrics.time_metrics,
1255 ),
1256 memory_comparison: Self::compare_memory_metrics(
1257 &profile1.metrics.memory_metrics,
1258 &profile2.metrics.memory_metrics,
1259 ),
1260 quality_comparison: Self::compare_quality_metrics(
1261 &profile1.metrics.quality_metrics,
1262 &profile2.metrics.quality_metrics,
1263 ),
1264 regressions: Vec::new(),
1265 improvements: Vec::new(),
1266 }
1267 }
1268
1269 fn compare_time_metrics(m1: &TimeMetrics, m2: &TimeMetrics) -> TimeComparison {
1271 TimeComparison {
1272 total_time_diff: m2.total_time.as_secs_f64() - m1.total_time.as_secs_f64(),
1273 total_time_ratio: m2.total_time.as_secs_f64() / m1.total_time.as_secs_f64(),
1274 qubo_time_diff: m2.qubo_generation_time.as_secs_f64()
1275 - m1.qubo_generation_time.as_secs_f64(),
1276 solving_time_diff: m2.solving_time.as_secs_f64() - m1.solving_time.as_secs_f64(),
1277 function_diffs: BTreeMap::new(), }
1279 }
1280
1281 fn compare_memory_metrics(m1: &MemoryMetrics, m2: &MemoryMetrics) -> MemoryComparison {
1283 MemoryComparison {
1284 peak_memory_diff: m2.peak_memory as i64 - m1.peak_memory as i64,
1285 peak_memory_ratio: m2.peak_memory as f64 / m1.peak_memory as f64,
1286 avg_memory_diff: m2.avg_memory as i64 - m1.avg_memory as i64,
1287 allocations_diff: m2.allocations as i64 - m1.allocations as i64,
1288 }
1289 }
1290
1291 fn compare_quality_metrics(m1: &QualityMetrics, m2: &QualityMetrics) -> QualityComparison {
1293 QualityComparison {
1294 convergence_rate_diff: m2.convergence_rate - m1.convergence_rate,
1295 time_to_best_diff: m2.time_to_best_solution.as_secs_f64()
1296 - m1.time_to_best_solution.as_secs_f64(),
1297 final_quality_diff: 0.0, }
1299 }
1300
1301 pub fn generate_report(
1303 &self,
1304 profile: &Profile,
1305 format: &OutputFormat,
1306 ) -> Result<String, String> {
1307 match format {
1308 OutputFormat::Json => Self::generate_json_report(profile),
1309 OutputFormat::Csv => Self::generate_csv_report(profile),
1310 OutputFormat::FlameGraph => Self::generate_flame_graph(profile),
1311 OutputFormat::ChromeTrace => Self::generate_chrome_trace(profile),
1312 OutputFormat::Binary => Err("Format not implemented".to_string()),
1313 }
1314 }
1315
1316 fn generate_json_report(profile: &Profile) -> Result<String, String> {
1318 use std::fmt::Write;
1319
1320 let mut json = String::new();
1321
1322 json.push_str("{\n");
1324
1325 json.push_str(" \"metadata\": {\n");
1327 writeln!(&mut json, " \"id\": \"{}\",", profile.id)
1328 .expect("Failed to write profile ID to JSON report");
1329 writeln!(
1330 &mut json,
1331 " \"start_time\": {},",
1332 profile.start_time.elapsed().as_millis()
1333 )
1334 .expect("Failed to write start_time to JSON report");
1335 if let Some(end_time) = profile.end_time {
1336 writeln!(
1337 &mut json,
1338 " \"end_time\": {},",
1339 end_time.elapsed().as_millis()
1340 )
1341 .expect("Failed to write end_time to JSON report");
1342 }
1343 json.push_str(" \"duration_ms\": ");
1344 write!(
1345 &mut json,
1346 "{}",
1347 profile.metrics.time_metrics.total_time.as_millis()
1348 )
1349 .expect("Failed to write duration_ms to JSON report");
1350 json.push_str("\n },\n");
1351
1352 json.push_str(" \"time_metrics\": {\n");
1354 writeln!(
1355 &mut json,
1356 " \"total_time_ms\": {},",
1357 profile.metrics.time_metrics.total_time.as_millis()
1358 )
1359 .expect("Failed to write total_time_ms to JSON report");
1360 writeln!(
1361 &mut json,
1362 " \"qubo_generation_ms\": {},",
1363 profile
1364 .metrics
1365 .time_metrics
1366 .qubo_generation_time
1367 .as_millis()
1368 )
1369 .expect("Failed to write qubo_generation_ms to JSON report");
1370 writeln!(
1371 &mut json,
1372 " \"compilation_ms\": {},",
1373 profile.metrics.time_metrics.compilation_time.as_millis()
1374 )
1375 .expect("Failed to write compilation_ms to JSON report");
1376 writeln!(
1377 &mut json,
1378 " \"solving_ms\": {},",
1379 profile.metrics.time_metrics.solving_time.as_millis()
1380 )
1381 .expect("Failed to write solving_ms to JSON report");
1382 writeln!(
1383 &mut json,
1384 " \"post_processing_ms\": {},",
1385 profile
1386 .metrics
1387 .time_metrics
1388 .post_processing_time
1389 .as_millis()
1390 )
1391 .expect("Failed to write post_processing_ms to JSON report");
1392
1393 json.push_str(" \"function_times\": {\n");
1395 let func_entries: Vec<_> = profile.metrics.time_metrics.function_times.iter().collect();
1396 for (i, (func, time)) in func_entries.iter().enumerate() {
1397 write!(&mut json, " \"{}\": {}", func, time.as_millis())
1398 .expect("Failed to write function time entry to JSON report");
1399 if i < func_entries.len() - 1 {
1400 json.push(',');
1401 }
1402 json.push('\n');
1403 }
1404 json.push_str(" },\n");
1405
1406 json.push_str(" \"percentiles_ms\": {\n");
1408 writeln!(
1409 &mut json,
1410 " \"p50\": {},",
1411 profile.metrics.time_metrics.percentiles.p50.as_millis()
1412 )
1413 .expect("Failed to write p50 percentile to JSON report");
1414 writeln!(
1415 &mut json,
1416 " \"p90\": {},",
1417 profile.metrics.time_metrics.percentiles.p90.as_millis()
1418 )
1419 .expect("Failed to write p90 percentile to JSON report");
1420 writeln!(
1421 &mut json,
1422 " \"p95\": {},",
1423 profile.metrics.time_metrics.percentiles.p95.as_millis()
1424 )
1425 .expect("Failed to write p95 percentile to JSON report");
1426 writeln!(
1427 &mut json,
1428 " \"p99\": {},",
1429 profile.metrics.time_metrics.percentiles.p99.as_millis()
1430 )
1431 .expect("Failed to write p99 percentile to JSON report");
1432 writeln!(
1433 &mut json,
1434 " \"p999\": {}",
1435 profile.metrics.time_metrics.percentiles.p999.as_millis()
1436 )
1437 .expect("Failed to write p999 percentile to JSON report");
1438 json.push_str(" }\n");
1439 json.push_str(" },\n");
1440
1441 json.push_str(" \"memory_metrics\": {\n");
1443 writeln!(
1444 &mut json,
1445 " \"peak_memory_bytes\": {},",
1446 profile.metrics.memory_metrics.peak_memory
1447 )
1448 .expect("Failed to write peak_memory_bytes to JSON report");
1449 writeln!(
1450 &mut json,
1451 " \"avg_memory_bytes\": {},",
1452 profile.metrics.memory_metrics.avg_memory
1453 )
1454 .expect("Failed to write avg_memory_bytes to JSON report");
1455 writeln!(
1456 &mut json,
1457 " \"allocations\": {},",
1458 profile.metrics.memory_metrics.allocations
1459 )
1460 .expect("Failed to write allocations to JSON report");
1461 writeln!(
1462 &mut json,
1463 " \"deallocations\": {},",
1464 profile.metrics.memory_metrics.deallocations
1465 )
1466 .expect("Failed to write deallocations to JSON report");
1467 writeln!(
1468 &mut json,
1469 " \"largest_allocation_bytes\": {}",
1470 profile.metrics.memory_metrics.largest_allocation
1471 )
1472 .expect("Failed to write largest_allocation_bytes to JSON report");
1473 json.push_str(" },\n");
1474
1475 json.push_str(" \"computation_metrics\": {\n");
1477 writeln!(
1478 &mut json,
1479 " \"flops\": {},",
1480 profile.metrics.computation_metrics.flops
1481 )
1482 .expect("Failed to write flops to JSON report");
1483 writeln!(
1484 &mut json,
1485 " \"memory_bandwidth_gbps\": {},",
1486 profile.metrics.computation_metrics.memory_bandwidth
1487 )
1488 .expect("Failed to write memory_bandwidth_gbps to JSON report");
1489 writeln!(
1490 &mut json,
1491 " \"cache_hit_rate\": {},",
1492 profile.metrics.computation_metrics.cache_hit_rate
1493 )
1494 .expect("Failed to write cache_hit_rate to JSON report");
1495 writeln!(
1496 &mut json,
1497 " \"branch_prediction_accuracy\": {},",
1498 profile
1499 .metrics
1500 .computation_metrics
1501 .branch_prediction_accuracy
1502 )
1503 .expect("Failed to write branch_prediction_accuracy to JSON report");
1504 writeln!(
1505 &mut json,
1506 " \"vectorization_efficiency\": {}",
1507 profile.metrics.computation_metrics.vectorization_efficiency
1508 )
1509 .expect("Failed to write vectorization_efficiency to JSON report");
1510 json.push_str(" },\n");
1511
1512 json.push_str(" \"quality_metrics\": {\n");
1514 writeln!(
1515 &mut json,
1516 " \"convergence_rate\": {},",
1517 profile.metrics.quality_metrics.convergence_rate
1518 )
1519 .expect("Failed to write convergence_rate to JSON report");
1520 writeln!(
1521 &mut json,
1522 " \"improvement_per_iteration\": {},",
1523 profile.metrics.quality_metrics.improvement_per_iteration
1524 )
1525 .expect("Failed to write improvement_per_iteration to JSON report");
1526 writeln!(
1527 &mut json,
1528 " \"time_to_first_solution_ms\": {},",
1529 profile
1530 .metrics
1531 .quality_metrics
1532 .time_to_first_solution
1533 .as_millis()
1534 )
1535 .expect("Failed to write time_to_first_solution_ms to JSON report");
1536 writeln!(
1537 &mut json,
1538 " \"time_to_best_solution_ms\": {},",
1539 profile
1540 .metrics
1541 .quality_metrics
1542 .time_to_best_solution
1543 .as_millis()
1544 )
1545 .expect("Failed to write time_to_best_solution_ms to JSON report");
1546
1547 json.push_str(" \"quality_timeline\": [\n");
1549 for (i, (time, quality)) in profile
1550 .metrics
1551 .quality_metrics
1552 .quality_timeline
1553 .iter()
1554 .enumerate()
1555 {
1556 json.push_str(" {\n");
1557 writeln!(&mut json, " \"time_ms\": {},", time.as_millis())
1558 .expect("Failed to write quality timeline time_ms to JSON report");
1559 writeln!(&mut json, " \"quality\": {quality}")
1560 .expect("Failed to write quality timeline quality value to JSON report");
1561 json.push_str(" }");
1562 if i < profile.metrics.quality_metrics.quality_timeline.len() - 1 {
1563 json.push(',');
1564 }
1565 json.push('\n');
1566 }
1567 json.push_str(" ]\n");
1568 json.push_str(" },\n");
1569
1570 json.push_str(" \"call_graph\": {\n");
1572 json.push_str(" \"nodes\": [\n");
1573 for (i, node) in profile.call_graph.nodes.iter().enumerate() {
1574 json.push_str(" {\n");
1575 writeln!(&mut json, " \"id\": {},", node.id)
1576 .expect("Failed to write call graph node id to JSON report");
1577 writeln!(
1578 &mut json,
1579 " \"name\": \"{}\",",
1580 node.name.replace('"', "\\\"")
1581 )
1582 .expect("Failed to write call graph node name to JSON report");
1583 writeln!(
1584 &mut json,
1585 " \"total_time_ms\": {},",
1586 node.total_time.as_millis()
1587 )
1588 .expect("Failed to write call graph node total_time_ms to JSON report");
1589 writeln!(
1590 &mut json,
1591 " \"self_time_ms\": {},",
1592 node.self_time.as_millis()
1593 )
1594 .expect("Failed to write call graph node self_time_ms to JSON report");
1595 writeln!(&mut json, " \"call_count\": {},", node.call_count)
1596 .expect("Failed to write call graph node call_count to JSON report");
1597 writeln!(
1598 &mut json,
1599 " \"avg_time_ms\": {}",
1600 node.avg_time.as_millis()
1601 )
1602 .expect("Failed to write call graph node avg_time_ms to JSON report");
1603 json.push_str(" }");
1604 if i < profile.call_graph.nodes.len() - 1 {
1605 json.push(',');
1606 }
1607 json.push('\n');
1608 }
1609 json.push_str(" ],\n");
1610
1611 json.push_str(" \"edges\": [\n");
1612 for (i, edge) in profile.call_graph.edges.iter().enumerate() {
1613 json.push_str(" {\n");
1614 writeln!(&mut json, " \"from\": {},", edge.from)
1615 .expect("Failed to write call graph edge from to JSON report");
1616 writeln!(&mut json, " \"to\": {},", edge.to)
1617 .expect("Failed to write call graph edge to to JSON report");
1618 writeln!(&mut json, " \"call_count\": {},", edge.call_count)
1619 .expect("Failed to write call graph edge call_count to JSON report");
1620 writeln!(
1621 &mut json,
1622 " \"total_time_ms\": {}",
1623 edge.total_time.as_millis()
1624 )
1625 .expect("Failed to write call graph edge total_time_ms to JSON report");
1626 json.push_str(" }");
1627 if i < profile.call_graph.edges.len() - 1 {
1628 json.push(',');
1629 }
1630 json.push('\n');
1631 }
1632 json.push_str(" ]\n");
1633 json.push_str(" },\n");
1634
1635 json.push_str(" \"events_summary\": {\n");
1637 writeln!(&mut json, " \"total_events\": {},", profile.events.len())
1638 .expect("Failed to write total_events to JSON report");
1639
1640 let mut event_counts = std::collections::BTreeMap::new();
1642 for event in &profile.events {
1643 let type_name = match &event.event_type {
1644 EventType::FunctionCall => "function_call",
1645 EventType::FunctionReturn => "function_return",
1646 EventType::MemoryAlloc => "memory_alloc",
1647 EventType::MemoryFree => "memory_free",
1648 EventType::IOOperation => "io_operation",
1649 EventType::Synchronization => "synchronization",
1650 EventType::Custom(name) => name,
1651 };
1652 *event_counts.entry(type_name).or_insert(0) += 1;
1653 }
1654
1655 json.push_str(" \"event_counts\": {\n");
1656 let count_entries: Vec<_> = event_counts.iter().collect();
1657 for (i, (event_type, count)) in count_entries.iter().enumerate() {
1658 write!(&mut json, " \"{event_type}\": {count}")
1659 .expect("Failed to write event count entry to JSON report");
1660 if i < count_entries.len() - 1 {
1661 json.push(',');
1662 }
1663 json.push('\n');
1664 }
1665 json.push_str(" }\n");
1666 json.push_str(" }\n");
1667
1668 json.push_str("}\n");
1669
1670 Ok(json)
1671 }
1672
1673 fn generate_csv_report(profile: &Profile) -> Result<String, String> {
1675 let mut csv = String::new();
1676
1677 csv.push_str("function,total_time_ms,call_count,avg_time_ms\n");
1678
1679 for node in &profile.call_graph.nodes {
1680 csv.push_str(&format!(
1681 "{},{},{},{}\n",
1682 node.name,
1683 node.total_time.as_millis(),
1684 node.call_count,
1685 node.avg_time.as_millis()
1686 ));
1687 }
1688
1689 Ok(csv)
1690 }
1691
1692 fn generate_flame_graph(profile: &Profile) -> Result<String, String> {
1694 let mut stacks = Vec::new();
1696
1697 for node in &profile.call_graph.nodes {
1698 let stack = vec![node.name.clone()];
1699 let value = node.self_time.as_micros() as usize;
1700 stacks.push((stack, value));
1701 }
1702
1703 Ok(format!("Flame graph with {} stacks", stacks.len()))
1705 }
1706
1707 fn generate_chrome_trace(profile: &Profile) -> Result<String, String> {
1709 #[derive(Serialize)]
1710 struct TraceEvent {
1711 name: String,
1712 cat: String,
1713 ph: String,
1714 ts: u64,
1715 dur: Option<u64>,
1716 pid: u32,
1717 tid: String,
1718 }
1719
1720 let mut events = Vec::new();
1721
1722 for event in &profile.events {
1723 let trace_event = TraceEvent {
1724 name: event.name.clone(),
1725 cat: "function".to_string(),
1726 ph: match event.event_type {
1727 EventType::FunctionCall => "B".to_string(),
1728 EventType::FunctionReturn => "E".to_string(),
1729 _ => "i".to_string(),
1730 },
1731 ts: event.timestamp.elapsed().as_micros() as u64,
1732 dur: event.duration.map(|d| d.as_micros() as u64),
1733 pid: std::process::id(),
1734 tid: format!("{:?}", event.thread_id),
1735 };
1736
1737 events.push(trace_event);
1738 }
1739
1740 serde_json::to_string(&events).map_err(|e| format!("JSON serialization error: {e}"))
1741 }
1742
1743 #[cfg(feature = "plotters")]
1745 pub fn visualize_profile(&self, profile: &Profile, output_path: &str) -> Result<(), String> {
1746 let root = BitMapBackend::new(output_path, (1024, 768)).into_drawing_area();
1747 root.fill(&WHITE)
1748 .map_err(|e| format!("Drawing error: {e}"))?;
1749
1750 let mut chart = ChartBuilder::on(&root)
1751 .caption("Performance Profile", ("sans-serif", 40))
1752 .margin(10)
1753 .x_label_area_size(30)
1754 .y_label_area_size(40)
1755 .build_cartesian_2d(
1756 0f64..profile.metrics.time_metrics.total_time.as_secs_f64(),
1757 0f64..100f64,
1758 )
1759 .map_err(|e| format!("Chart error: {e}"))?;
1760
1761 chart
1762 .configure_mesh()
1763 .draw()
1764 .map_err(|e| format!("Mesh error: {e}"))?;
1765
1766 if !profile.resource_usage.cpu_usage.is_empty() {
1768 let cpu_data: Vec<(f64, f64)> = profile
1769 .resource_usage
1770 .cpu_usage
1771 .iter()
1772 .map(|(t, usage)| (t.duration_since(profile.start_time).as_secs_f64(), *usage))
1773 .collect();
1774
1775 chart
1776 .draw_series(LineSeries::new(cpu_data, &RED))
1777 .map_err(|e| format!("Series error: {e}"))?
1778 .label("CPU Usage")
1779 .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 10, y)], RED));
1780 }
1781
1782 chart
1783 .configure_series_labels()
1784 .background_style(WHITE.mix(0.8))
1785 .border_style(BLACK)
1786 .draw()
1787 .map_err(|e| format!("Legend error: {e}"))?;
1788
1789 root.present().map_err(|e| format!("Present error: {e}"))?;
1790
1791 Ok(())
1792 }
1793}
1794
1795pub struct FunctionGuard {
1797 profiler: Option<*mut PerformanceProfiler>,
1798 name: String,
1799}
1800
1801impl Drop for FunctionGuard {
1802 fn drop(&mut self) {
1803 if let Some(profiler_ptr) = self.profiler {
1804 unsafe {
1805 (*profiler_ptr).exit_function(&self.name);
1806 }
1807 }
1808 }
1809}
1810
1811unsafe impl Send for FunctionGuard {}
1812
1813impl PerformanceAnalyzer {
1814 pub fn new(config: AnalysisConfig) -> Self {
1816 Self {
1817 config,
1818 bottleneck_detector: BottleneckDetector {
1819 hot_function_threshold: 0.1, detect_memory_leaks: true,
1821 detect_contention: true,
1822 },
1823 optimization_suggester: OptimizationSuggester {
1824 rules: Self::default_optimization_rules(),
1825 history: Vec::new(),
1826 },
1827 }
1828 }
1829
1830 fn default_optimization_rules() -> Vec<OptimizationRule> {
1832 vec![
1833 OptimizationRule {
1834 name: "Hot function optimization".to_string(),
1835 condition: RuleCondition::HighFunctionTime {
1836 function: "any".to_string(),
1837 threshold: Duration::from_millis(100),
1838 },
1839 suggestion: "Consider optimizing this function or caching results".to_string(),
1840 improvement: 0.2,
1841 },
1842 OptimizationRule {
1843 name: "Memory optimization".to_string(),
1844 condition: RuleCondition::HighMemoryUsage {
1845 threshold: 1024 * 1024 * 1024, },
1847 suggestion: "Consider using more memory-efficient data structures".to_string(),
1848 improvement: 0.15,
1849 },
1850 OptimizationRule {
1851 name: "Cache optimization".to_string(),
1852 condition: RuleCondition::LowCacheHitRate { threshold: 0.8 },
1853 suggestion: "Consider improving data locality or cache-friendly algorithms"
1854 .to_string(),
1855 improvement: 0.1,
1856 },
1857 ]
1858 }
1859
1860 pub fn analyze(&self, profile: &Profile) -> AnalysisReport {
1862 let mut report = AnalysisReport {
1863 bottlenecks: Vec::new(),
1864 optimizations: Vec::new(),
1865 anomalies: Vec::new(),
1866 summary: AnalysisSummary {
1867 total_time: profile.metrics.time_metrics.total_time,
1868 peak_memory: profile.metrics.memory_metrics.peak_memory,
1869 hot_functions: Vec::new(),
1870 critical_path: Vec::new(),
1871 },
1872 };
1873
1874 if self.config.detect_bottlenecks {
1876 report.bottlenecks = self.detect_bottlenecks(profile);
1877 }
1878
1879 if self.config.suggest_optimizations {
1881 report.optimizations = self.suggest_optimizations(profile);
1882 }
1883
1884 if self.config.detect_anomalies {
1886 report.anomalies = Self::detect_anomalies(profile);
1887 }
1888
1889 report.summary.hot_functions = Self::find_hot_functions(profile);
1891
1892 report.summary.critical_path = Self::find_critical_path(profile);
1894
1895 report
1896 }
1897
1898 fn detect_bottlenecks(&self, profile: &Profile) -> Vec<Bottleneck> {
1900 let mut bottlenecks = Vec::new();
1901
1902 for node in &profile.call_graph.nodes {
1904 let time_percentage = node.total_time.as_secs_f64()
1905 / profile.metrics.time_metrics.total_time.as_secs_f64();
1906
1907 if time_percentage > self.bottleneck_detector.hot_function_threshold {
1908 bottlenecks.push(Bottleneck {
1909 bottleneck_type: BottleneckType::CPU,
1910 location: node.name.clone(),
1911 severity: if time_percentage > 0.5 {
1912 Severity::High
1913 } else if time_percentage > 0.3 {
1914 Severity::Medium
1915 } else {
1916 Severity::Low
1917 },
1918 impact: time_percentage,
1919 description: format!(
1920 "Function uses {:.1}% of total time",
1921 time_percentage * 100.0
1922 ),
1923 });
1924 }
1925 }
1926
1927 if self.bottleneck_detector.detect_memory_leaks {
1929 let alloc_dealloc_diff = profile.metrics.memory_metrics.allocations as i64
1930 - profile.metrics.memory_metrics.deallocations as i64;
1931
1932 if alloc_dealloc_diff > 1000 {
1933 bottlenecks.push(Bottleneck {
1934 bottleneck_type: BottleneckType::Memory,
1935 location: "global".to_string(),
1936 severity: Severity::High,
1937 impact: alloc_dealloc_diff as f64
1938 / profile.metrics.memory_metrics.allocations as f64,
1939 description: format!(
1940 "Potential memory leak: {alloc_dealloc_diff} unfreed allocations"
1941 ),
1942 });
1943 }
1944 }
1945
1946 bottlenecks
1947 }
1948
1949 fn suggest_optimizations(&self, profile: &Profile) -> Vec<OptimizationSuggestion> {
1951 let mut suggestions = Vec::new();
1952
1953 for rule in &self.optimization_suggester.rules {
1954 if Self::check_rule_condition(&rule.condition, profile) {
1955 suggestions.push(OptimizationSuggestion {
1956 title: rule.name.clone(),
1957 description: rule.suggestion.clone(),
1958 expected_improvement: rule.improvement,
1959 implementation_effort: ImplementationEffort::Medium,
1960 priority: Priority::High,
1961 });
1962 }
1963 }
1964
1965 suggestions
1966 }
1967
1968 fn check_rule_condition(condition: &RuleCondition, profile: &Profile) -> bool {
1970 match condition {
1971 RuleCondition::HighFunctionTime {
1972 function,
1973 threshold,
1974 } => {
1975 if function == "any" {
1976 profile
1977 .call_graph
1978 .nodes
1979 .iter()
1980 .any(|n| n.total_time > *threshold)
1981 } else {
1982 profile
1983 .call_graph
1984 .nodes
1985 .iter()
1986 .any(|n| n.name == *function && n.total_time > *threshold)
1987 }
1988 }
1989 RuleCondition::HighMemoryUsage { threshold } => {
1990 profile.metrics.memory_metrics.peak_memory > *threshold
1991 }
1992 RuleCondition::LowCacheHitRate { threshold } => {
1993 profile.metrics.computation_metrics.cache_hit_rate < *threshold
1994 }
1995 RuleCondition::Custom(_) => false,
1996 }
1997 }
1998
1999 fn detect_anomalies(profile: &Profile) -> Vec<Anomaly> {
2001 let mut anomalies = Vec::new();
2002
2003 for node in &profile.call_graph.nodes {
2005 if node.call_count > 10 {
2006 let avg_time = node.avg_time.as_secs_f64();
2007 let total_time = node.total_time.as_secs_f64();
2008 let expected_total = avg_time * node.call_count as f64;
2009
2010 if (total_time - expected_total).abs() / expected_total > 0.5 {
2011 anomalies.push(Anomaly {
2012 anomaly_type: AnomalyType::Performance,
2013 location: node.name.clone(),
2014 description: "Unusual time distribution detected".to_string(),
2015 confidence: 0.8,
2016 });
2017 }
2018 }
2019 }
2020
2021 anomalies
2022 }
2023
2024 fn find_hot_functions(profile: &Profile) -> Vec<(String, f64)> {
2026 let total_time = profile.metrics.time_metrics.total_time.as_secs_f64();
2027
2028 let mut hot_functions: Vec<_> = profile
2029 .call_graph
2030 .nodes
2031 .iter()
2032 .map(|n| (n.name.clone(), n.total_time.as_secs_f64() / total_time))
2033 .collect();
2034
2035 hot_functions.sort_by(|a, b| {
2036 b.1.partial_cmp(&a.1)
2037 .expect("Failed to compare time percentages in hot function sorting")
2038 });
2039 hot_functions.truncate(10);
2040
2041 hot_functions
2042 }
2043
2044 fn find_critical_path(profile: &Profile) -> Vec<String> {
2046 let mut path = Vec::new();
2048
2049 if let Some(&root) = profile.call_graph.roots.first() {
2050 let mut current = root;
2051 path.push(profile.call_graph.nodes[current].name.clone());
2052
2053 while let Some(edge) = profile
2055 .call_graph
2056 .edges
2057 .iter()
2058 .filter(|e| e.from == current)
2059 .max_by_key(|e| profile.call_graph.nodes[e.to].total_time)
2060 {
2061 current = edge.to;
2062 path.push(profile.call_graph.nodes[current].name.clone());
2063 }
2064 }
2065
2066 path
2067 }
2068}
2069
2070#[derive(Debug, Clone)]
2072pub struct AnalysisReport {
2073 pub bottlenecks: Vec<Bottleneck>,
2074 pub optimizations: Vec<OptimizationSuggestion>,
2075 pub anomalies: Vec<Anomaly>,
2076 pub summary: AnalysisSummary,
2077}
2078
2079#[derive(Debug, Clone)]
2080pub struct Bottleneck {
2081 pub bottleneck_type: BottleneckType,
2082 pub location: String,
2083 pub severity: Severity,
2084 pub impact: f64,
2085 pub description: String,
2086}
2087
2088#[derive(Debug, Clone)]
2089pub enum BottleneckType {
2090 CPU,
2091 Memory,
2092 IO,
2093 Network,
2094 Contention,
2095}
2096
2097#[derive(Debug, Clone)]
2098pub enum Severity {
2099 Low,
2100 Medium,
2101 High,
2102 Critical,
2103}
2104
2105#[derive(Debug, Clone)]
2106pub struct OptimizationSuggestion {
2107 pub title: String,
2108 pub description: String,
2109 pub expected_improvement: f64,
2110 pub implementation_effort: ImplementationEffort,
2111 pub priority: Priority,
2112}
2113
2114#[derive(Debug, Clone)]
2115pub enum ImplementationEffort {
2116 Low,
2117 Medium,
2118 High,
2119}
2120
2121#[derive(Debug, Clone)]
2122pub enum Priority {
2123 Low,
2124 Medium,
2125 High,
2126 Critical,
2127}
2128
2129#[derive(Debug, Clone)]
2130pub struct Anomaly {
2131 pub anomaly_type: AnomalyType,
2132 pub location: String,
2133 pub description: String,
2134 pub confidence: f64,
2135}
2136
2137#[derive(Debug, Clone)]
2138pub enum AnomalyType {
2139 Performance,
2140 Memory,
2141 Behavior,
2142}
2143
2144#[derive(Debug, Clone)]
2145pub struct AnalysisSummary {
2146 pub total_time: Duration,
2147 pub peak_memory: usize,
2148 pub hot_functions: Vec<(String, f64)>,
2149 pub critical_path: Vec<String>,
2150}
2151
2152#[derive(Debug, Clone)]
2154pub struct ComparisonReport {
2155 pub time_comparison: TimeComparison,
2156 pub memory_comparison: MemoryComparison,
2157 pub quality_comparison: QualityComparison,
2158 pub regressions: Vec<Regression>,
2159 pub improvements: Vec<Improvement>,
2160}
2161
2162#[derive(Debug, Clone)]
2163pub struct TimeComparison {
2164 pub total_time_diff: f64,
2165 pub total_time_ratio: f64,
2166 pub qubo_time_diff: f64,
2167 pub solving_time_diff: f64,
2168 pub function_diffs: BTreeMap<String, f64>,
2169}
2170
2171#[derive(Debug, Clone)]
2172pub struct MemoryComparison {
2173 pub peak_memory_diff: i64,
2174 pub peak_memory_ratio: f64,
2175 pub avg_memory_diff: i64,
2176 pub allocations_diff: i64,
2177}
2178
2179#[derive(Debug, Clone)]
2180pub struct QualityComparison {
2181 pub convergence_rate_diff: f64,
2182 pub time_to_best_diff: f64,
2183 pub final_quality_diff: f64,
2184}
2185
2186#[derive(Debug, Clone)]
2187pub struct Regression {
2188 pub metric: String,
2189 pub old_value: f64,
2190 pub new_value: f64,
2191 pub change_percentage: f64,
2192 pub severity: Severity,
2193}
2194
2195#[derive(Debug, Clone)]
2196pub struct Improvement {
2197 pub metric: String,
2198 pub old_value: f64,
2199 pub new_value: f64,
2200 pub change_percentage: f64,
2201}
2202
2203pub struct RealTimeMonitor {
2205 sampling_interval: Duration,
2207 collector_names: Vec<String>,
2209 live_metrics: Arc<Mutex<LiveMetrics>>,
2211 _monitor_handle: Option<thread::JoinHandle<()>>,
2213}
2214
2215impl RealTimeMonitor {
2216 pub fn new(sampling_interval: Duration, collector_names: Vec<String>) -> Result<Self, String> {
2217 let live_metrics = Arc::new(Mutex::new(LiveMetrics::default()));
2218
2219 Ok(Self {
2220 sampling_interval,
2221 collector_names,
2222 live_metrics,
2223 _monitor_handle: None,
2224 })
2225 }
2226
2227 pub fn get_live_metrics(&self) -> LiveMetrics {
2228 self.live_metrics
2229 .lock()
2230 .expect("Failed to acquire lock on live_metrics for reading")
2231 .clone()
2232 }
2233}
2234
2235#[derive(Debug, Clone, Default)]
2236pub struct LiveMetrics {
2237 pub current_cpu: f64,
2238 pub current_memory: usize,
2239 pub current_functions: Vec<(String, Duration)>,
2240 pub events_per_second: f64,
2241 pub last_update: Option<Instant>,
2242}
2243
2244pub struct PerformancePredictor {
2246 history: Vec<Profile>,
2248 model: PredictionModel,
2250}
2251
2252impl PerformancePredictor {
2253 pub fn new(profiles: &[Profile]) -> Self {
2254 Self {
2255 history: profiles.to_vec(),
2256 model: PredictionModel::Linear,
2257 }
2258 }
2259
2260 pub fn predict(&self, characteristics: &ProblemCharacteristics) -> PerformancePrediction {
2261 let base_time = Duration::from_millis(100);
2263 let complexity_factor = match characteristics.complexity {
2264 ProblemComplexity::Linear => characteristics.size as f64,
2265 ProblemComplexity::Quadratic => (characteristics.size as f64).powi(2),
2266 ProblemComplexity::Exponential => 2.0_f64.powi(characteristics.size.min(30) as i32),
2267 };
2268
2269 let estimated_time = base_time.mul_f64(complexity_factor / 1000.0);
2270 let estimated_memory = characteristics.size * 8; PerformancePrediction {
2273 estimated_runtime: estimated_time,
2274 estimated_memory,
2275 confidence: if self.history.len() > 5 { 0.8 } else { 0.5 },
2276 bottleneck_predictions: vec![
2277 BottleneckPrediction {
2278 location: "QUBO generation".to_string(),
2279 probability: 0.3,
2280 predicted_impact: 0.4,
2281 },
2282 BottleneckPrediction {
2283 location: "Solving".to_string(),
2284 probability: 0.7,
2285 predicted_impact: 0.6,
2286 },
2287 ],
2288 }
2289 }
2290}
2291
2292#[derive(Debug, Clone)]
2293pub enum PredictionModel {
2294 Linear,
2295 Polynomial,
2296 MachineLearning,
2297}
2298
2299#[derive(Debug, Clone)]
2300pub struct ProblemCharacteristics {
2301 pub size: usize,
2302 pub complexity: ProblemComplexity,
2303 pub sparsity: f64,
2304 pub symmetry: bool,
2305 pub structure: ProblemStructure,
2306}
2307
2308#[derive(Debug, Clone)]
2309pub enum ProblemComplexity {
2310 Linear,
2311 Quadratic,
2312 Exponential,
2313}
2314
2315#[derive(Debug, Clone)]
2316pub enum ProblemStructure {
2317 Dense,
2318 Sparse,
2319 Structured,
2320 Random,
2321}
2322
2323#[derive(Debug, Clone)]
2324pub struct PerformancePrediction {
2325 pub estimated_runtime: Duration,
2326 pub estimated_memory: usize,
2327 pub confidence: f64,
2328 pub bottleneck_predictions: Vec<BottleneckPrediction>,
2329}
2330
2331#[derive(Debug, Clone)]
2332pub struct BottleneckPrediction {
2333 pub location: String,
2334 pub probability: f64,
2335 pub predicted_impact: f64,
2336}
2337
2338#[derive(Debug, Clone)]
2340pub struct OptimizationRecommendation {
2341 pub title: String,
2342 pub description: String,
2343 pub category: RecommendationCategory,
2344 pub impact: RecommendationImpact,
2345 pub effort: ImplementationEffort,
2346 pub estimated_improvement: f64,
2347 pub code_suggestions: Vec<String>,
2348}
2349
2350#[derive(Debug, Clone)]
2351pub enum RecommendationCategory {
2352 Algorithm,
2353 Memory,
2354 IO,
2355 Parallelization,
2356 Caching,
2357 DataStructure,
2358}
2359
2360#[derive(Debug, Clone)]
2361pub enum RecommendationImpact {
2362 Low,
2363 Medium,
2364 High,
2365 Critical,
2366}
2367
2368#[derive(Debug, Clone)]
2370pub enum ExternalTool {
2371 Perf,
2372 Valgrind,
2373 FlameScope,
2374 SpeedScope,
2375}
2376
2377pub struct ContinuousProfiler {
2379 duration: Duration,
2380 sampling_interval: Duration,
2381 profiles: Vec<Profile>,
2382}
2383
2384impl ContinuousProfiler {
2385 pub const fn new(duration: Duration, sampling_interval: Duration) -> Self {
2386 Self {
2387 duration,
2388 sampling_interval,
2389 profiles: Vec::new(),
2390 }
2391 }
2392
2393 pub fn get_profiles(&self) -> &[Profile] {
2394 &self.profiles
2395 }
2396}
2397
2398#[derive(Debug, Clone)]
2400pub struct BenchmarkComparison {
2401 pub profiles: Vec<String>,
2402 pub metrics_comparison: Vec<MetricComparison>,
2403 pub regression_analysis: Vec<RegressionAnalysis>,
2404 pub performance_trends: Vec<PerformanceTrendAnalysis>,
2405}
2406
2407#[derive(Debug, Clone)]
2408pub struct MetricComparison {
2409 pub metric_name: String,
2410 pub values: Vec<f64>,
2411 pub trend: PerformanceTrend,
2412 pub variance: f64,
2413}
2414
2415#[derive(Debug, Clone)]
2416pub enum PerformanceTrend {
2417 Improving,
2418 Stable,
2419 Degrading,
2420 Unknown,
2421}
2422
2423#[derive(Debug, Clone)]
2424pub struct RegressionAnalysis {
2425 pub metric: String,
2426 pub regression_coefficient: f64,
2427 pub correlation: f64,
2428 pub prediction_accuracy: f64,
2429}
2430
2431#[derive(Debug, Clone)]
2432pub struct PerformanceTrendAnalysis {
2433 pub function_name: String,
2434 pub trend: PerformanceTrend,
2435 pub rate_of_change: f64,
2436 pub statistical_significance: f64,
2437}
2438
2439struct TimeCollector;
2441
2442impl MetricsCollector for TimeCollector {
2443 fn collect(&self) -> Result<MetricsSample, String> {
2444 Ok(MetricsSample {
2445 timestamp: Instant::now(),
2446 values: HashMap::new(),
2447 })
2448 }
2449
2450 fn name(&self) -> &'static str {
2451 "TimeCollector"
2452 }
2453
2454 fn supported_metrics(&self) -> Vec<MetricType> {
2455 vec![MetricType::Time]
2456 }
2457}
2458
2459struct MemoryCollector;
2460
2461impl MetricsCollector for MemoryCollector {
2462 fn collect(&self) -> Result<MetricsSample, String> {
2463 let mut values = HashMap::new();
2465 values.insert(MetricType::Memory, 0.0);
2466
2467 Ok(MetricsSample {
2468 timestamp: Instant::now(),
2469 values,
2470 })
2471 }
2472
2473 fn name(&self) -> &'static str {
2474 "MemoryCollector"
2475 }
2476
2477 fn supported_metrics(&self) -> Vec<MetricType> {
2478 vec![MetricType::Memory]
2479 }
2480}
2481
2482struct CPUCollector;
2483
2484impl MetricsCollector for CPUCollector {
2485 fn collect(&self) -> Result<MetricsSample, String> {
2486 let mut values = HashMap::new();
2488 values.insert(MetricType::CPU, 0.0);
2489
2490 Ok(MetricsSample {
2491 timestamp: Instant::now(),
2492 values,
2493 })
2494 }
2495
2496 fn name(&self) -> &'static str {
2497 "CPUCollector"
2498 }
2499
2500 fn supported_metrics(&self) -> Vec<MetricType> {
2501 vec![MetricType::CPU]
2502 }
2503}
2504
2505#[macro_export]
2507macro_rules! profile {
2508 ($profiler:expr, $name:expr) => {
2509 $profiler.enter_function($name)
2510 };
2511}
2512
2513#[macro_export]
2514macro_rules! time_it {
2515 ($profiler:expr, $name:expr, $code:block) => {{
2516 $profiler.start_timer($name);
2517 let mut result = $code;
2518 $profiler.stop_timer($name);
2519 result
2520 }};
2521}
2522
2523#[cfg(test)]
2524mod tests {
2525 use super::*;
2526
2527 #[test]
2528 fn test_performance_profiler() {
2529 let mut config = ProfilerConfig {
2530 enabled: true,
2531 sampling_interval: Duration::from_millis(10),
2532 metrics: vec![MetricType::Time, MetricType::Memory],
2533 profile_memory: true,
2534 profile_cpu: true,
2535 profile_gpu: false,
2536 detailed_timing: true,
2537 output_format: OutputFormat::Json,
2538 auto_save_interval: None,
2539 };
2540
2541 let mut profiler = PerformanceProfiler::new(config);
2542
2543 let mut result = profiler.start_profile("test_profile");
2545 assert!(result.is_ok());
2546
2547 {
2549 let _guard = profiler.enter_function("test_function");
2550 profiler.start_timer("computation");
2551 thread::sleep(Duration::from_millis(10));
2552 profiler.stop_timer("computation");
2553
2554 profiler.record_allocation(1024);
2555 profiler.record_solution_quality(0.5);
2556 profiler.record_deallocation(1024);
2557 }
2558
2559 let profile = profiler.stop_profile();
2561 assert!(profile.is_ok());
2562
2563 let profile = profile.expect("Failed to stop profiling in test_performance_profiler");
2564 assert!(!profile.events.is_empty());
2565 assert!(profile.metrics.time_metrics.total_time > Duration::from_secs(0));
2566
2567 let mut report = profiler.analyze_profile(&profile);
2569 assert!(report.summary.total_time > Duration::from_secs(0));
2570
2571 let json_report = profiler.generate_report(&profile, &OutputFormat::Json);
2573 assert!(json_report.is_ok());
2574 }
2575}