1#![allow(dead_code)]
290use std::collections::HashMap;
291use std::time::{Duration, Instant};
292use torsh_core::error::Result;
293use torsh_nn::Module;
294use torsh_profiler::{ProfileEvent, Profiler};
295
296#[derive(Debug, Clone)]
301pub struct BottleneckReport {
302 pub total_time: Duration,
303 pub layer_times: Vec<LayerTiming>,
304 pub operation_times: HashMap<String, OperationTiming>,
305 pub memory_peaks: Vec<MemoryPeak>,
306 pub recommendations: Vec<String>,
307
308 pub flame_graph: Option<FlameGraphData>,
310 pub memory_profile: MemoryProfileData,
311 pub gpu_profile: Option<GpuProfileData>,
312 pub call_stack_analysis: CallStackAnalysis,
313 pub performance_regression: Option<RegressionAnalysis>,
314 pub hotspot_analysis: HotspotAnalysis,
315}
316
317#[derive(Debug, Clone)]
319pub struct FlameGraphData {
320 pub root_frame: FlameFrame,
321 pub total_samples: usize,
322 pub sample_rate_hz: f32,
323 pub duration_ms: f32,
324}
325
326#[derive(Debug, Clone)]
328pub struct FlameFrame {
329 pub name: String,
330 pub file: Option<String>,
331 pub line: Option<u32>,
332 pub self_time_ms: f32,
333 pub total_time_ms: f32,
334 pub sample_count: usize,
335 pub children: Vec<FlameFrame>,
336}
337
338#[derive(Debug, Clone)]
340pub struct MemoryProfileData {
341 pub peak_usage_mb: f32,
343 pub current_usage_mb: f32,
345 pub allocation_timeline: Vec<MemorySnapshot>,
346 pub memory_leaks: Option<Vec<MemoryLeak>>,
351 pub fragmentation_ratio: Option<f32>,
354 pub gc_pressure: Option<f32>,
355 pub memory_bandwidth_utilization: Option<f32>,
358 pub cache_performance: CachePerformance,
359}
360
361#[derive(Debug, Clone)]
363pub struct MemorySnapshot {
364 pub timestamp_ms: f32,
365 pub allocated_mb: f32,
366 pub reserved_mb: f32,
367 pub active_allocations: usize,
368 pub largest_free_block_mb: f32,
369}
370
371#[derive(Debug, Clone)]
373pub struct MemoryLeak {
374 pub allocation_site: String,
375 pub size_mb: f32,
376 pub age_ms: f32,
377 pub stack_trace: Vec<String>,
378}
379
380#[derive(Debug, Clone)]
388pub struct CachePerformance {
389 pub l1_hit_rate: Option<f32>,
390 pub l2_hit_rate: Option<f32>,
391 pub l3_hit_rate: Option<f32>,
392 pub cache_misses_per_instruction: Option<f32>,
393 pub memory_stalls_percentage: Option<f32>,
394}
395
396#[derive(Debug, Clone)]
398pub struct GpuProfileData {
399 pub utilization_percentage: f32,
400 pub memory_utilization_percentage: f32,
401 pub temperature_celsius: f32,
402 pub power_consumption_watts: f32,
403 pub kernel_executions: Vec<GpuKernelExecution>,
404 pub memory_transfers: Vec<GpuMemoryTransfer>,
405 pub compute_capability: String,
406 pub occupancy_percentage: f32,
407}
408
409#[derive(Debug, Clone)]
411pub struct GpuKernelExecution {
412 pub kernel_name: String,
413 pub duration_ms: f32,
414 pub grid_size: (u32, u32, u32),
415 pub block_size: (u32, u32, u32),
416 pub registers_per_thread: u32,
417 pub shared_memory_kb: f32,
418 pub occupancy: f32,
419}
420
421#[derive(Debug, Clone)]
423pub struct GpuMemoryTransfer {
424 pub direction: MemoryTransferDirection,
425 pub size_mb: f32,
426 pub duration_ms: f32,
427 pub bandwidth_gb_s: f32,
428}
429
430#[derive(Debug, Clone)]
432pub enum MemoryTransferDirection {
433 HostToDevice,
434 DeviceToHost,
435 DeviceToDevice,
436 Unified,
437}
438
439#[derive(Debug, Clone)]
441pub struct CallStackAnalysis {
442 pub hottest_paths: Vec<CallPath>,
443 pub recursive_calls: Vec<RecursiveCall>,
444 pub call_frequency: HashMap<String, usize>,
445 pub average_stack_depth: f32,
446 pub max_stack_depth: usize,
447}
448
449#[derive(Debug, Clone)]
451pub struct CallPath {
452 pub path: Vec<String>,
453 pub total_time_ms: f32,
454 pub call_count: usize,
455 pub average_time_ms: f32,
456}
457
458#[derive(Debug, Clone)]
460pub struct RecursiveCall {
461 pub function_name: String,
462 pub max_depth: usize,
463 pub total_recursive_time_ms: f32,
464}
465
466#[derive(Debug, Clone)]
468pub struct RegressionAnalysis {
469 pub baseline_performance: PerformanceMetrics,
470 pub current_performance: PerformanceMetrics,
471 pub regression_percentage: f32,
472 pub regressed_operations: Vec<String>,
473 pub improvements: Vec<String>,
474}
475
476#[derive(Debug, Clone)]
478pub struct PerformanceMetrics {
479 pub total_time_ms: f32,
480 pub memory_usage_mb: f32,
481 pub throughput_ops_per_sec: f32,
482 pub energy_consumption_mj: Option<f32>,
483}
484
485#[derive(Debug, Clone)]
487pub struct HotspotAnalysis {
488 pub cpu_hotspots: Vec<Hotspot>,
489 pub memory_hotspots: Vec<MemoryHotspot>,
490 pub io_hotspots: Vec<IoHotspot>,
491 pub synchronization_hotspots: Vec<SyncHotspot>,
492}
493
494#[derive(Debug, Clone)]
496pub struct Hotspot {
497 pub function_name: String,
498 pub time_percentage: f32,
499 pub instruction_count: Option<u64>,
500 pub cache_misses: Option<u64>,
501 pub branch_mispredictions: Option<u64>,
502}
503
504#[derive(Debug, Clone)]
506pub struct MemoryHotspot {
507 pub operation: String,
508 pub access_pattern: MemoryAccessPattern,
509 pub bandwidth_utilization: f32,
510 pub latency_ms: f32,
511}
512
513#[derive(Debug, Clone)]
515pub enum MemoryAccessPattern {
516 Sequential,
517 Random,
518 Strided { stride: usize },
519 Clustered,
520}
521
522#[derive(Debug, Clone)]
524pub struct IoHotspot {
525 pub operation_type: String,
526 pub wait_time_ms: f32,
527 pub throughput_mb_s: f32,
528 pub queue_depth: usize,
529}
530
531#[derive(Debug, Clone)]
533pub struct SyncHotspot {
534 pub synchronization_type: String,
535 pub wait_time_ms: f32,
536 pub contention_count: usize,
537 pub affected_threads: usize,
538}
539
540#[derive(Debug, Clone)]
542pub struct LayerTiming {
543 pub name: String,
544 pub module_type: String,
545 pub forward_time: Duration,
546 pub backward_time: Option<Duration>,
547 pub percentage: f32,
548 pub num_params: usize,
549}
550
551#[derive(Debug, Clone)]
553pub struct OperationTiming {
554 pub count: usize,
555 pub total_time: Duration,
556 pub avg_time: Duration,
557 pub min_time: Duration,
558 pub max_time: Duration,
559}
560
561#[derive(Debug, Clone)]
563pub struct MemoryPeak {
564 pub operation: String,
565 pub allocated_mb: f32,
566 pub reserved_mb: f32,
567}
568
569#[derive(Debug, Clone)]
571pub struct AdvancedProfilingConfig {
572 pub enable_flame_graph: bool,
573 pub enable_memory_profiling: bool,
574 pub enable_gpu_profiling: bool,
575 pub enable_call_stack_analysis: bool,
576 pub enable_regression_detection: bool,
577 pub enable_hotspot_analysis: bool,
578 pub sample_rate_hz: f32,
579 pub memory_snapshot_interval_ms: f32,
580}
581
582impl Default for AdvancedProfilingConfig {
583 fn default() -> Self {
584 Self {
585 enable_flame_graph: true,
586 enable_memory_profiling: true,
587 enable_gpu_profiling: false, enable_call_stack_analysis: true,
589 enable_regression_detection: false,
590 enable_hotspot_analysis: true,
591 sample_rate_hz: 1000.0,
592 memory_snapshot_interval_ms: 10.0,
593 }
594 }
595}
596
597pub fn profile_bottlenecks<M: Module>(
599 model: &M,
600 input_shape: &[usize],
601 num_iterations: usize,
602 profile_backward: bool,
603) -> Result<BottleneckReport> {
604 let config = AdvancedProfilingConfig {
605 enable_flame_graph: false,
606 enable_memory_profiling: true,
607 enable_gpu_profiling: false,
608 enable_call_stack_analysis: false,
609 enable_regression_detection: false,
610 enable_hotspot_analysis: false,
611 ..Default::default()
612 };
613
614 profile_bottlenecks_advanced(model, input_shape, num_iterations, profile_backward, config)
615}
616
617pub fn profile_bottlenecks_advanced<M: Module>(
619 model: &M,
620 input_shape: &[usize],
621 num_iterations: usize,
622 profile_backward: bool,
623 config: AdvancedProfilingConfig,
624) -> Result<BottleneckReport> {
625 let mut profiler = Profiler::new();
627 let mut memory_collector = MemoryMetricsCollector::new();
628 let mut leak_detector = LeakDetector::new();
629
630 profiler.start();
632
633 if config.enable_memory_profiling {
634 memory_collector.start_collection();
635 leak_detector.enable();
636 }
637
638 let layer_times = Vec::new();
640 let mut operation_times: HashMap<String, Vec<Duration>> = HashMap::new();
641 let mut memory_peaks = Vec::new();
642 let mut memory_snapshots = Vec::new();
643 let mut call_stacks: Vec<(Vec<String>, Duration)> = Vec::new();
647
648 let gpu_profiler = if config.enable_gpu_profiling {
650 setup_gpu_profiling()
651 } else {
652 None
653 };
654
655 for _ in 0..3 {
657 let input = torsh_tensor::creation::randn(input_shape)?;
658 let _ = model.forward(&input)?;
659 }
660
661 let start_time = Instant::now();
663 let snapshot_interval = Duration::from_millis(config.memory_snapshot_interval_ms as u64);
664 let mut last_snapshot = Instant::now();
665
666 for i in 0..num_iterations {
667 let input = torsh_tensor::creation::randn(input_shape)?;
668 let iteration_start = Instant::now();
669
670 let call_stack = if config.enable_call_stack_analysis {
674 Some(capture_call_stack())
675 } else {
676 None
677 };
678
679 let forward_start = Instant::now();
681 let output = model.forward(&input)?;
682 let forward_time = forward_start.elapsed();
683
684 operation_times
685 .entry("forward".to_string())
686 .or_default()
687 .push(forward_time);
688
689 if profile_backward && output.requires_grad() {
691 let backward_start = Instant::now();
692 output.sum()?.backward()?;
693 let backward_time = backward_start.elapsed();
694
695 operation_times
696 .entry("backward".to_string())
697 .or_default()
698 .push(backward_time);
699 }
700
701 if let Some(call_stack) = call_stack {
702 call_stacks.push((call_stack, iteration_start.elapsed()));
703 }
704
705 if config.enable_memory_profiling && last_snapshot.elapsed() >= snapshot_interval {
707 if let Ok(memory_info) = get_detailed_memory_info() {
708 memory_snapshots.push(MemorySnapshot {
709 timestamp_ms: start_time.elapsed().as_millis() as f32,
710 allocated_mb: memory_info.0,
711 reserved_mb: memory_info.1,
712 active_allocations: memory_info.2,
713 largest_free_block_mb: memory_info.3,
714 });
715 }
716 memory_collector.sample();
719 last_snapshot = Instant::now();
720 }
721
722 if i % 10 == 0 {
724 if let Ok(memory_info) = get_memory_info() {
725 memory_peaks.push(MemoryPeak {
726 operation: format!("iteration_{}", i),
727 allocated_mb: memory_info.0,
728 reserved_mb: memory_info.1,
729 });
730 }
731 }
732 }
733
734 let total_time = start_time.elapsed();
735
736 profiler.stop();
738
739 if config.enable_memory_profiling {
740 memory_collector.stop_collection();
741 }
742
743 let flame_graph = if config.enable_flame_graph {
747 Some(generate_flame_graph(&operation_times, total_time))
748 } else {
749 None
750 };
751
752 let memory_profile = if config.enable_memory_profiling {
753 generate_memory_profile(&memory_collector, &leak_detector, memory_snapshots)?
754 } else {
755 MemoryProfileData::default()
756 };
757
758 let gpu_profile = if let Some(gpu_prof) = gpu_profiler {
759 Some(collect_gpu_profile_data(gpu_prof)?)
760 } else {
761 None
762 };
763
764 let call_stack_analysis = if config.enable_call_stack_analysis {
765 analyze_call_stacks(call_stacks)?
766 } else {
767 CallStackAnalysis::default()
768 };
769
770 let hotspot_analysis = if config.enable_hotspot_analysis {
771 analyze_hotspots(&operation_times, total_time)
772 } else {
773 HotspotAnalysis::default()
774 };
775
776 let processed_op_times = process_operation_times(operation_times);
778
779 let recommendations = generate_advanced_recommendations(
781 &layer_times,
782 &processed_op_times,
783 &memory_peaks,
784 &memory_profile,
785 &hotspot_analysis,
786 );
787
788 Ok(BottleneckReport {
789 total_time,
790 layer_times,
791 operation_times: processed_op_times,
792 memory_peaks,
793 recommendations,
794 flame_graph,
795 memory_profile,
796 gpu_profile,
797 call_stack_analysis,
798 performance_regression: None,
799 hotspot_analysis,
800 })
801}
802
803fn generate_flame_graph(
813 operation_times: &HashMap<String, Vec<Duration>>,
814 total_time: Duration,
815) -> FlameGraphData {
816 let samples = real_profile_samples(operation_times);
817 let total_samples = samples.len();
818 let sample_rate_hz = if total_time.as_secs_f32() > 0.0 {
821 total_samples as f32 / total_time.as_secs_f32()
822 } else {
823 0.0
824 };
825
826 let root_frame = build_flame_graph_tree(samples);
827
828 FlameGraphData {
829 root_frame,
830 total_samples,
831 sample_rate_hz,
832 duration_ms: total_time.as_millis() as f32,
833 }
834}
835
836fn real_profile_samples(operation_times: &HashMap<String, Vec<Duration>>) -> Vec<ProfileSample> {
841 let mut samples = Vec::new();
842 for (name, durations) in operation_times {
843 for duration in durations {
844 samples.push(ProfileSample {
845 function_name: name.clone(),
846 duration_ms: duration.as_secs_f32() * 1000.0,
847 stack_trace: vec![name.clone()],
848 });
849 }
850 }
851 samples
852}
853
854fn build_flame_graph_tree(samples: Vec<ProfileSample>) -> FlameFrame {
857 let mut root = FlameFrame {
858 name: "root".to_string(),
859 file: None,
860 line: None,
861 self_time_ms: 0.0,
862 total_time_ms: 0.0,
863 sample_count: samples.len(),
864 children: Vec::new(),
865 };
866
867 let mut function_stats: HashMap<String, (f32, usize)> = HashMap::new();
871 for sample in &samples {
872 let entry = function_stats
873 .entry(sample.function_name.clone())
874 .or_insert((0.0, 0));
875 entry.0 += sample.duration_ms;
876 entry.1 += 1;
877 }
878
879 for (function_name, (total_time, sample_count)) in function_stats {
881 let child_frame = FlameFrame {
882 name: function_name,
883 file: None,
884 line: None,
885 self_time_ms: total_time,
886 total_time_ms: total_time,
887 sample_count,
888 children: Vec::new(),
889 };
890 root.children.push(child_frame);
891 root.total_time_ms += total_time;
892 }
893
894 root
895}
896
897#[derive(Debug, Clone)]
899struct ProfileSample {
900 function_name: String,
901 duration_ms: f32,
902 stack_trace: Vec<String>,
903}
904
905fn generate_memory_profile(
907 collector: &MemoryMetricsCollector,
908 leak_detector: &LeakDetector,
909 snapshots: Vec<MemorySnapshot>,
910) -> Result<MemoryProfileData> {
911 let metrics = collector.get_metrics();
912
913 let memory_leaks = leak_detector.get_detected_leaks().map(|leaks| {
917 leaks
918 .into_iter()
919 .map(|leak| MemoryLeak {
920 allocation_site: leak.location,
921 size_mb: leak.size_bytes as f32 / 1024.0 / 1024.0,
922 age_ms: leak.age_ms,
923 stack_trace: leak.stack_trace,
924 })
925 .collect()
926 });
927
928 Ok(MemoryProfileData {
929 peak_usage_mb: metrics.peak_usage_mb,
930 current_usage_mb: metrics.current_usage_mb,
931 allocation_timeline: snapshots,
932 memory_leaks,
933 fragmentation_ratio: metrics.fragmentation_ratio,
934 gc_pressure: None,
935 memory_bandwidth_utilization: metrics.bandwidth_utilization,
936 cache_performance: CachePerformance {
937 l1_hit_rate: metrics.l1_hit_rate,
938 l2_hit_rate: metrics.l2_hit_rate,
939 l3_hit_rate: metrics.l3_hit_rate,
940 cache_misses_per_instruction: metrics.cache_misses_per_instruction,
941 memory_stalls_percentage: metrics.memory_stalls_percentage,
942 },
943 })
944}
945
946#[derive(Debug)]
949struct MemoryMetrics {
950 peak_usage_mb: f32,
951 current_usage_mb: f32,
952 fragmentation_ratio: Option<f32>,
953 bandwidth_utilization: Option<f32>,
954 l1_hit_rate: Option<f32>,
955 l2_hit_rate: Option<f32>,
956 l3_hit_rate: Option<f32>,
957 cache_misses_per_instruction: Option<f32>,
958 memory_stalls_percentage: Option<f32>,
959}
960
961#[derive(Debug)]
964struct DetectedLeak {
965 location: String,
966 size_bytes: usize,
967 age_ms: f32,
968 stack_trace: Vec<String>,
969}
970
971#[allow(dead_code)]
973fn analyze_layer_timings(_events: &[ProfileEvent], _total_time: Duration) -> Vec<LayerTiming> {
974 Vec::new()
976}
977
978fn process_operation_times(
980 raw_times: HashMap<String, Vec<Duration>>,
981) -> HashMap<String, OperationTiming> {
982 raw_times
983 .into_iter()
984 .map(|(name, times)| {
985 let count = times.len();
986 let total_time: Duration = times.iter().sum();
987 let avg_time = total_time / count as u32;
988 let min_time = times.iter().min().copied().unwrap_or(Duration::ZERO);
989 let max_time = times.iter().max().copied().unwrap_or(Duration::ZERO);
990
991 (
992 name,
993 OperationTiming {
994 count,
995 total_time,
996 avg_time,
997 min_time,
998 max_time,
999 },
1000 )
1001 })
1002 .collect()
1003}
1004
1005fn get_memory_info() -> Result<(f32, f32)> {
1008 #[cfg(target_os = "linux")]
1009 {
1010 let status = std::fs::read_to_string("/proc/self/status").map_err(|e| {
1011 torsh_core::TorshError::IoError(format!("Failed to read /proc/self/status: {}", e))
1012 })?;
1013 let mut rss_kb: Option<u64> = None;
1014 let mut vmsize_kb: Option<u64> = None;
1015 for line in status.lines() {
1016 if let Some(rest) = line.strip_prefix("VmRSS:") {
1017 rss_kb = rest.split_whitespace().next().and_then(|s| s.parse().ok());
1018 } else if let Some(rest) = line.strip_prefix("VmSize:") {
1019 vmsize_kb = rest.split_whitespace().next().and_then(|s| s.parse().ok());
1020 }
1021 if rss_kb.is_some() && vmsize_kb.is_some() {
1022 break;
1023 }
1024 }
1025 let rss_mb = rss_kb.unwrap_or(0) as f32 / 1024.0;
1026 let vmsize_mb = vmsize_kb.unwrap_or(0) as f32 / 1024.0;
1027 return Ok((rss_mb, vmsize_mb));
1028 }
1029 #[cfg(not(target_os = "linux"))]
1030 {
1031 Ok((0.0, 0.0))
1033 }
1034}
1035
1036fn get_detailed_memory_info() -> Result<(f32, f32, usize, f32)> {
1040 #[cfg(target_os = "linux")]
1041 {
1042 let status = std::fs::read_to_string("/proc/self/status").map_err(|e| {
1044 torsh_core::TorshError::IoError(format!("Failed to read /proc/self/status: {}", e))
1045 })?;
1046 let mut rss_kb: Option<u64> = None;
1047 let mut vmsize_kb: Option<u64> = None;
1048 for line in status.lines() {
1049 if let Some(rest) = line.strip_prefix("VmRSS:") {
1050 rss_kb = rest.split_whitespace().next().and_then(|s| s.parse().ok());
1051 } else if let Some(rest) = line.strip_prefix("VmSize:") {
1052 vmsize_kb = rest.split_whitespace().next().and_then(|s| s.parse().ok());
1053 }
1054 if rss_kb.is_some() && vmsize_kb.is_some() {
1055 break;
1056 }
1057 }
1058 let allocated_mb = rss_kb.unwrap_or(0) as f32 / 1024.0;
1059 let reserved_mb = vmsize_kb.unwrap_or(0) as f32 / 1024.0;
1060
1061 let active_allocations = std::fs::read_to_string("/proc/self/maps")
1063 .map(|s| s.lines().count())
1064 .unwrap_or(0);
1065
1066 let meminfo = std::fs::read_to_string("/proc/meminfo").map_err(|e| {
1068 torsh_core::TorshError::IoError(format!("Failed to read /proc/meminfo: {}", e))
1069 })?;
1070 let mut memfree_kb: Option<u64> = None;
1071 for line in meminfo.lines() {
1072 if let Some(rest) = line.strip_prefix("MemFree:") {
1073 memfree_kb = rest.split_whitespace().next().and_then(|s| s.parse().ok());
1074 break;
1075 }
1076 }
1077 let free_mb = memfree_kb.unwrap_or(0) as f32 / 1024.0;
1078
1079 return Ok((allocated_mb, reserved_mb, active_allocations, free_mb));
1080 }
1081 #[cfg(not(target_os = "linux"))]
1082 {
1083 Ok((0.0, 0.0, 0, 0.0))
1085 }
1086}
1087
1088fn setup_gpu_profiling() -> Option<GpuProfiler> {
1090 None
1093}
1094
1095struct GpuProfiler {
1097 _context: String,
1098}
1099
1100fn collect_gpu_profile_data(_profiler: GpuProfiler) -> Result<GpuProfileData> {
1107 Ok(GpuProfileData {
1108 utilization_percentage: 0.0,
1109 memory_utilization_percentage: 0.0,
1110 temperature_celsius: 0.0,
1111 power_consumption_watts: 0.0,
1112 kernel_executions: vec![],
1113 memory_transfers: vec![],
1114 compute_capability: "unknown".to_string(),
1115 occupancy_percentage: 0.0,
1116 })
1117}
1118
1119fn capture_call_stack() -> Vec<String> {
1131 let backtrace = std::backtrace::Backtrace::force_capture();
1132 format!("{backtrace}")
1133 .lines()
1134 .map(|line| line.trim())
1135 .filter(|line| !line.is_empty())
1136 .map(|line| line.to_string())
1137 .collect()
1138}
1139
1140fn analyze_call_stacks(call_stacks: Vec<(Vec<String>, Duration)>) -> Result<CallStackAnalysis> {
1147 let mut call_frequency = HashMap::new();
1148 let mut total_depth = 0;
1149 let mut max_depth = 0;
1150
1151 for (stack, _duration) in &call_stacks {
1152 total_depth += stack.len();
1153 max_depth = max_depth.max(stack.len());
1154
1155 for function in stack {
1156 *call_frequency.entry(function.clone()).or_insert(0) += 1;
1157 }
1158 }
1159
1160 let average_stack_depth = if !call_stacks.is_empty() {
1161 total_depth as f32 / call_stacks.len() as f32
1162 } else {
1163 0.0
1164 };
1165
1166 let mut path_groups: HashMap<Vec<String>, Vec<f32>> = HashMap::new();
1170 for (stack, duration) in call_stacks {
1171 path_groups
1172 .entry(stack)
1173 .or_default()
1174 .push(duration.as_secs_f32() * 1000.0);
1175 }
1176
1177 let mut hottest_paths: Vec<CallPath> = path_groups
1178 .into_iter()
1179 .map(|(path, times_ms)| {
1180 let call_count = times_ms.len();
1181 let total_time_ms: f32 = times_ms.iter().sum();
1182 let average_time_ms = total_time_ms / call_count as f32;
1183 CallPath {
1184 path,
1185 total_time_ms,
1186 call_count,
1187 average_time_ms,
1188 }
1189 })
1190 .collect();
1191 hottest_paths.sort_by(|a, b| {
1192 b.total_time_ms
1193 .partial_cmp(&a.total_time_ms)
1194 .unwrap_or(std::cmp::Ordering::Equal)
1195 });
1196 hottest_paths.truncate(5);
1197
1198 Ok(CallStackAnalysis {
1199 hottest_paths,
1200 recursive_calls: vec![], call_frequency,
1202 average_stack_depth,
1203 max_stack_depth: max_depth,
1204 })
1205}
1206
1207fn analyze_hotspots(
1221 operation_times: &HashMap<String, Vec<Duration>>,
1222 total_time: Duration,
1223) -> HotspotAnalysis {
1224 let total_secs = total_time.as_secs_f32();
1225 let mut cpu_hotspots: Vec<Hotspot> = operation_times
1226 .iter()
1227 .map(|(name, durations)| {
1228 let op_total_secs: f32 = durations.iter().map(|d| d.as_secs_f32()).sum();
1229 let time_percentage = if total_secs > 0.0 {
1230 (op_total_secs / total_secs) * 100.0
1231 } else {
1232 0.0
1233 };
1234 Hotspot {
1235 function_name: name.clone(),
1236 time_percentage,
1237 instruction_count: None,
1238 cache_misses: None,
1239 branch_mispredictions: None,
1240 }
1241 })
1242 .collect();
1243 cpu_hotspots.sort_by(|a, b| {
1244 b.time_percentage
1245 .partial_cmp(&a.time_percentage)
1246 .unwrap_or(std::cmp::Ordering::Equal)
1247 });
1248
1249 HotspotAnalysis {
1250 cpu_hotspots,
1251 memory_hotspots: vec![],
1253 io_hotspots: vec![],
1255 synchronization_hotspots: vec![],
1257 }
1258}
1259
1260fn generate_advanced_recommendations(
1262 layer_times: &[LayerTiming],
1263 operation_times: &HashMap<String, OperationTiming>,
1264 memory_peaks: &[MemoryPeak],
1265 memory_profile: &MemoryProfileData,
1266 hotspot_analysis: &HotspotAnalysis,
1267) -> Vec<String> {
1268 let mut recommendations = Vec::new();
1269
1270 recommendations.extend(generate_recommendations(
1272 layer_times,
1273 operation_times,
1274 memory_peaks,
1275 ));
1276
1277 if let Some(fragmentation_ratio) = memory_profile.fragmentation_ratio {
1280 if fragmentation_ratio > 0.3 {
1281 recommendations.push(format!(
1282 "High memory fragmentation ({:.1}%). Consider using memory pools or reducing allocation frequency.",
1283 fragmentation_ratio * 100.0
1284 ));
1285 }
1286 }
1287
1288 if let Some(leaks) = &memory_profile.memory_leaks {
1289 if !leaks.is_empty() {
1290 recommendations.push(format!(
1291 "Detected {} memory leaks. Review allocation sites: {}",
1292 leaks.len(),
1293 leaks
1294 .iter()
1295 .take(3)
1296 .map(|leak| leak.allocation_site.as_str())
1297 .collect::<Vec<_>>()
1298 .join(", ")
1299 ));
1300 }
1301 }
1302
1303 if let Some(l1_hit_rate) = memory_profile.cache_performance.l1_hit_rate {
1304 if l1_hit_rate < 0.9 {
1305 recommendations.push(format!(
1306 "Low L1 cache hit rate ({:.1}%). Consider improving data locality and access patterns.",
1307 l1_hit_rate * 100.0
1308 ));
1309 }
1310 }
1311
1312 for hotspot in &hotspot_analysis.cpu_hotspots {
1314 if hotspot.time_percentage > 20.0 {
1315 recommendations.push(format!(
1316 "Function '{}' consumes {:.1}% of CPU time. Consider optimizing this function.",
1317 hotspot.function_name, hotspot.time_percentage
1318 ));
1319
1320 if let Some(cache_misses) = hotspot.cache_misses {
1321 if cache_misses > 100_000 {
1322 recommendations.push(format!(
1323 "High cache miss rate in '{}'. Optimize memory access patterns.",
1324 hotspot.function_name
1325 ));
1326 }
1327 }
1328 }
1329 }
1330
1331 for mem_hotspot in &hotspot_analysis.memory_hotspots {
1333 match mem_hotspot.access_pattern {
1334 MemoryAccessPattern::Random => {
1335 recommendations.push(format!(
1336 "Random memory access detected in '{}'. Consider restructuring data layout for better locality.",
1337 mem_hotspot.operation
1338 ));
1339 }
1340 MemoryAccessPattern::Strided { stride } => {
1341 if stride > 64 {
1342 recommendations.push(format!(
1343 "Large stride ({}) in memory access for '{}'. Consider data reorganization.",
1344 stride, mem_hotspot.operation
1345 ));
1346 }
1347 }
1348 _ => {}
1349 }
1350
1351 if mem_hotspot.bandwidth_utilization < 50.0 {
1352 recommendations.push(format!(
1353 "Low memory bandwidth utilization ({:.1}%) in '{}'. Consider vectorization or prefetching.",
1354 mem_hotspot.bandwidth_utilization, mem_hotspot.operation
1355 ));
1356 }
1357 }
1358
1359 for io_hotspot in &hotspot_analysis.io_hotspots {
1361 if io_hotspot.wait_time_ms > 10.0 {
1362 recommendations.push(format!(
1363 "High I/O wait time ({:.1}ms) for '{}'. Consider async I/O or data prefetching.",
1364 io_hotspot.wait_time_ms, io_hotspot.operation_type
1365 ));
1366 }
1367 }
1368
1369 for sync_hotspot in &hotspot_analysis.synchronization_hotspots {
1371 if sync_hotspot.wait_time_ms > 5.0 {
1372 recommendations.push(format!(
1373 "Synchronization bottleneck in '{}' ({:.1}ms wait time). Consider lock-free algorithms or finer-grained locking.",
1374 sync_hotspot.synchronization_type, sync_hotspot.wait_time_ms
1375 ));
1376 }
1377 }
1378
1379 recommendations
1380}
1381
1382impl Default for MemoryProfileData {
1384 fn default() -> Self {
1391 Self {
1392 peak_usage_mb: 0.0,
1393 current_usage_mb: 0.0,
1394 allocation_timeline: vec![],
1395 memory_leaks: None,
1396 fragmentation_ratio: None,
1397 gc_pressure: None,
1398 memory_bandwidth_utilization: None,
1399 cache_performance: CachePerformance {
1400 l1_hit_rate: None,
1401 l2_hit_rate: None,
1402 l3_hit_rate: None,
1403 cache_misses_per_instruction: None,
1404 memory_stalls_percentage: None,
1405 },
1406 }
1407 }
1408}
1409
1410impl Default for CallStackAnalysis {
1411 fn default() -> Self {
1412 Self {
1413 hottest_paths: vec![],
1414 recursive_calls: vec![],
1415 call_frequency: HashMap::new(),
1416 average_stack_depth: 0.0,
1417 max_stack_depth: 0,
1418 }
1419 }
1420}
1421
1422impl Default for HotspotAnalysis {
1423 fn default() -> Self {
1424 Self {
1425 cpu_hotspots: vec![],
1426 memory_hotspots: vec![],
1427 io_hotspots: vec![],
1428 synchronization_hotspots: vec![],
1429 }
1430 }
1431}
1432
1433trait MemoryCollectorTrait {
1434 fn new() -> Self;
1435 fn start_collection(&mut self);
1436 fn stop_collection(&mut self);
1437 fn get_metrics(&self) -> MemoryMetrics;
1438}
1439
1440impl MemoryCollectorTrait for MemoryMetricsCollector {
1441 fn new() -> Self {
1442 MemoryMetricsCollector {
1443 #[cfg(feature = "collect_env")]
1444 peak_bytes: 0,
1445 }
1446 }
1447
1448 fn start_collection(&mut self) {
1449 #[cfg(feature = "collect_env")]
1450 {
1451 self.peak_bytes = current_process_memory_bytes().unwrap_or(0);
1452 }
1453 }
1454
1455 fn stop_collection(&mut self) {
1456 self.sample();
1459 }
1460
1461 fn get_metrics(&self) -> MemoryMetrics {
1462 #[cfg(feature = "collect_env")]
1463 {
1464 let current_bytes = current_process_memory_bytes().unwrap_or(0);
1465 let peak_bytes = self.peak_bytes.max(current_bytes);
1466 MemoryMetrics {
1467 peak_usage_mb: bytes_to_mb(peak_bytes),
1468 current_usage_mb: bytes_to_mb(current_bytes),
1469 fragmentation_ratio: None,
1472 bandwidth_utilization: None,
1473 l1_hit_rate: None,
1474 l2_hit_rate: None,
1475 l3_hit_rate: None,
1476 cache_misses_per_instruction: None,
1477 memory_stalls_percentage: None,
1478 }
1479 }
1480 #[cfg(not(feature = "collect_env"))]
1481 {
1482 MemoryMetrics {
1483 peak_usage_mb: 0.0,
1484 current_usage_mb: 0.0,
1485 fragmentation_ratio: None,
1486 bandwidth_utilization: None,
1487 l1_hit_rate: None,
1488 l2_hit_rate: None,
1489 l3_hit_rate: None,
1490 cache_misses_per_instruction: None,
1491 memory_stalls_percentage: None,
1492 }
1493 }
1494 }
1495}
1496
1497impl MemoryMetricsCollector {
1498 fn sample(&mut self) {
1502 #[cfg(feature = "collect_env")]
1503 {
1504 if let Some(bytes) = current_process_memory_bytes() {
1505 self.peak_bytes = self.peak_bytes.max(bytes);
1506 }
1507 }
1508 }
1509}
1510
1511struct MemoryMetricsCollector {
1522 #[cfg(feature = "collect_env")]
1525 peak_bytes: u64,
1526}
1527
1528#[cfg(feature = "collect_env")]
1532fn current_process_memory_bytes() -> Option<u64> {
1533 use sysinfo::{ProcessesToUpdate, System};
1534
1535 let pid = sysinfo::get_current_pid().ok()?;
1536 let mut sys = System::new();
1537 sys.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
1538 sys.process(pid).map(|process| process.memory())
1539}
1540
1541#[cfg(feature = "collect_env")]
1542fn bytes_to_mb(bytes: u64) -> f32 {
1543 bytes as f32 / (1024.0 * 1024.0)
1544}
1545
1546trait LeakDetectorTrait {
1547 fn new() -> Self;
1548 fn enable(&mut self);
1549 fn get_detected_leaks(&self) -> Option<Vec<DetectedLeak>>;
1550}
1551
1552impl LeakDetectorTrait for LeakDetector {
1553 fn new() -> Self {
1554 LeakDetector { _placeholder: () }
1555 }
1556
1557 fn enable(&mut self) {
1558 }
1560
1561 fn get_detected_leaks(&self) -> Option<Vec<DetectedLeak>> {
1567 None
1568 }
1569}
1570
1571impl LeakDetector {
1572 fn new() -> Self {
1573 Self { _placeholder: () }
1574 }
1575}
1576
1577struct LeakDetector {
1578 _placeholder: (),
1579}
1580
1581fn generate_recommendations(
1583 layer_times: &[LayerTiming],
1584 operation_times: &HashMap<String, OperationTiming>,
1585 memory_peaks: &[MemoryPeak],
1586) -> Vec<String> {
1587 let mut recommendations = Vec::new();
1588
1589 if let Some(slowest) = layer_times.first() {
1591 if slowest.percentage > 30.0 {
1592 recommendations.push(format!(
1593 "Layer '{}' takes {:.1}% of total time. Consider optimizing or replacing this layer.",
1594 slowest.name, slowest.percentage
1595 ));
1596 }
1597 }
1598
1599 if let (Some(forward), Some(backward)) = (
1601 operation_times.get("forward"),
1602 operation_times.get("backward"),
1603 ) {
1604 let ratio = backward.avg_time.as_secs_f32() / forward.avg_time.as_secs_f32();
1605 if ratio > 3.0 {
1606 recommendations.push(format!(
1607 "Backward pass is {:.1}x slower than forward pass. Consider gradient checkpointing.",
1608 ratio
1609 ));
1610 }
1611 }
1612
1613 if !memory_peaks.is_empty() {
1615 let max_memory = memory_peaks
1616 .iter()
1617 .map(|p| p.allocated_mb)
1618 .fold(0.0f32, |a, b| a.max(b));
1619
1620 if max_memory > 1000.0 {
1621 recommendations.push(format!(
1622 "High memory usage detected ({:.1} MB). Consider using mixed precision training.",
1623 max_memory
1624 ));
1625 }
1626 }
1627
1628 for layer in layer_times.iter().take(5) {
1630 if layer.module_type.contains("Conv") && layer.percentage > 20.0 {
1631 recommendations.push(format!(
1632 "Convolution layer '{}' is slow. Consider using depthwise separable convolutions.",
1633 layer.name
1634 ));
1635 }
1636 }
1637
1638 recommendations
1639}
1640
1641pub fn print_bottleneck_report(report: &BottleneckReport) {
1643 println!("=== Bottleneck Analysis Report ===");
1644 println!();
1645 println!(
1646 "Total profiling time: {:.3}s",
1647 report.total_time.as_secs_f32()
1648 );
1649 println!();
1650
1651 println!("Top 10 Slowest Layers:");
1652 println!(
1653 "{:<30} {:<15} {:<10} {:<10} {:<10}",
1654 "Layer", "Type", "Forward", "Backward", "% Time"
1655 );
1656 println!("{}", "-".repeat(75));
1657
1658 for layer in report.layer_times.iter().take(10) {
1659 let backward_str = layer
1660 .backward_time
1661 .map(|t| format!("{:.3}ms", t.as_secs_f32() * 1000.0))
1662 .unwrap_or_else(|| "N/A".to_string());
1663
1664 println!(
1665 "{:<30} {:<15} {:<10.3}ms {:<10} {:<10.1}%",
1666 layer.name,
1667 layer.module_type,
1668 layer.forward_time.as_secs_f32() * 1000.0,
1669 backward_str,
1670 layer.percentage
1671 );
1672 }
1673 println!();
1674
1675 println!("Operation Summary:");
1676 for (name, timing) in &report.operation_times {
1677 println!(
1678 "{}: {} calls, avg {:.3}ms, total {:.3}s",
1679 name,
1680 timing.count,
1681 timing.avg_time.as_secs_f32() * 1000.0,
1682 timing.total_time.as_secs_f32()
1683 );
1684 }
1685 println!();
1686
1687 println!("Memory Profile:");
1688 println!(
1689 " Peak usage: {:.1} MB, current usage: {:.1} MB",
1690 report.memory_profile.peak_usage_mb, report.memory_profile.current_usage_mb
1691 );
1692 println!(
1693 " Fragmentation ratio: {}",
1694 format_optional_percent(report.memory_profile.fragmentation_ratio)
1695 );
1696 println!(
1697 " Memory leaks: {}",
1698 match &report.memory_profile.memory_leaks {
1699 Some(leaks) => format!("{}", leaks.len()),
1700 None => "not measured".to_string(),
1701 }
1702 );
1703 let cache = &report.memory_profile.cache_performance;
1704 println!(
1705 " Cache hit rate: L1 {}, L2 {}, L3 {}",
1706 format_optional_percent(cache.l1_hit_rate),
1707 format_optional_percent(cache.l2_hit_rate),
1708 format_optional_percent(cache.l3_hit_rate)
1709 );
1710 println!();
1711
1712 if !report.recommendations.is_empty() {
1713 println!("Optimization Recommendations:");
1714 for (i, rec) in report.recommendations.iter().enumerate() {
1715 println!("{}. {}", i + 1, rec);
1716 }
1717 }
1718}
1719
1720fn format_optional_percent(value: Option<f32>) -> String {
1724 value
1725 .map(|v| format!("{:.1}%", v * 100.0))
1726 .unwrap_or_else(|| "not measured".to_string())
1727}
1728
1729#[cfg(test)]
1730mod tests {
1731 use super::*;
1732
1733 #[test]
1734 fn test_get_memory_info_nonnegative() {
1735 let (alloc, reserved) = get_memory_info().unwrap_or((0.0, 0.0));
1736 assert!(
1737 alloc >= 0.0,
1738 "allocated MB should be non-negative, got {}",
1739 alloc
1740 );
1741 assert!(
1742 reserved >= 0.0,
1743 "reserved MB should be non-negative, got {}",
1744 reserved
1745 );
1746 #[cfg(target_os = "linux")]
1747 {
1748 assert!(
1749 alloc > 0.0,
1750 "allocated MB should be positive on Linux, got {}",
1751 alloc
1752 );
1753 }
1754 }
1755
1756 #[test]
1757 fn test_get_detailed_memory_info_nonnegative() {
1758 let (alloc, reserved, active_allocs, free_mb) =
1759 get_detailed_memory_info().unwrap_or((0.0, 0.0, 0, 0.0));
1760 assert!(
1761 alloc >= 0.0,
1762 "allocated MB should be non-negative, got {}",
1763 alloc
1764 );
1765 assert!(
1766 reserved >= 0.0,
1767 "reserved MB should be non-negative, got {}",
1768 reserved
1769 );
1770 assert!(
1771 free_mb >= 0.0,
1772 "free MB should be non-negative, got {}",
1773 free_mb
1774 );
1775 #[cfg(target_os = "linux")]
1776 {
1777 assert!(
1778 alloc > 0.0,
1779 "allocated MB should be positive on Linux, got {}",
1780 alloc
1781 );
1782 assert!(
1783 active_allocs > 0,
1784 "active allocations should be positive on Linux, got {}",
1785 active_allocs
1786 );
1787 }
1788 let _ = active_allocs; }
1790
1791 #[test]
1792 fn test_process_operation_times() {
1793 let mut raw_times = HashMap::new();
1794 raw_times.insert(
1795 "test_op".to_string(),
1796 vec![
1797 Duration::from_millis(10),
1798 Duration::from_millis(20),
1799 Duration::from_millis(15),
1800 ],
1801 );
1802
1803 let processed = process_operation_times(raw_times);
1804 let timing = processed.get("test_op").unwrap();
1805
1806 assert_eq!(timing.count, 3);
1807 assert_eq!(timing.total_time, Duration::from_millis(45));
1808 assert_eq!(timing.avg_time, Duration::from_millis(15));
1809 assert_eq!(timing.min_time, Duration::from_millis(10));
1810 assert_eq!(timing.max_time, Duration::from_millis(20));
1811 }
1812}