Skip to main content

torsh_utils/
bottleneck.rs

1//! # Advanced Performance Bottleneck Profiling
2//!
3//! This module provides sophisticated profiling capabilities for identifying and analyzing
4//! performance bottlenecks in deep learning models. It goes beyond simple timing to provide
5//! deep insights into memory usage, GPU utilization, and execution patterns.
6//!
7//! ## Features
8//!
9//! - **Flame Graph Generation**: built from real per-iteration forward/backward
10//!   timings (coarse-grained: whole-pass, not per-layer, since `Module::forward`
11//!   is opaque to this crate)
12//! - **Memory Profiling**: real peak/current process memory via `sysinfo`.
13//!   Leak detection and cache/fragmentation metrics are not implemented (they
14//!   would require allocator instrumentation or hardware performance
15//!   counters) and are reported as `None`, never a fabricated number
16//! - **GPU Profiling**: reports zeros with an explicit "not measured" doc note
17//!   unless a real GPU backend is wired up (no NVML/CUDA linkage here)
18//! - **Hotspot Detection**: real CPU time-share per timed operation. Memory/
19//!   I/O/synchronization hotspots require instrumentation this crate does not
20//!   have and are always empty
21//! - **Call Stack Analysis**: real stack captures (`std::backtrace`) paired
22//!   with each iteration's real duration
23//! - **Regression Detection**: compare against baseline performance metrics
24//! - **Cache Performance**: not measured (requires hardware performance
25//!   counters); always reported as `None`, not a plausible-looking number
26//!
27//! ## Quick Start
28//!
29//! ### Basic Profiling
30//!
31//! ```rust,no_run
32//! use torsh_utils::bottleneck::{profile_bottlenecks, print_bottleneck_report};
33//! # use torsh_nn::Module;
34//! # struct MyModel;
35//! # impl Module for MyModel {
36//! #   fn forward(&self, _: &torsh_tensor::Tensor) -> Result<torsh_tensor::Tensor, torsh_core::TorshError> {
37//! #     unimplemented!()
38//! #   }
39//! # }
40//!
41//! # fn example() -> Result<(), torsh_core::TorshError> {
42//! let model = MyModel;
43//!
44//! // Profile model execution
45//! let report = profile_bottlenecks(
46//!     &model,
47//!     &[1, 3, 224, 224],  // Input shape
48//!     100,                 // Number of iterations
49//!     true                 // Profile backward pass
50//! )?;
51//!
52//! // Print comprehensive report
53//! print_bottleneck_report(&report);
54//!
55//! // Access specific data
56//! println!("Total time: {:?}", report.total_time);
57//! println!("Peak memory: {:.1} MB", report.memory_profile.peak_usage_mb);
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! ### Advanced Profiling with Flame Graphs
63//!
64//! ```rust,no_run
65//! use torsh_utils::bottleneck::{profile_bottlenecks_advanced, AdvancedProfilingConfig};
66//! # use torsh_nn::Module;
67//! # struct MyModel;
68//! # impl Module for MyModel {
69//! #   fn forward(&self, _: &torsh_tensor::Tensor) -> Result<torsh_tensor::Tensor, torsh_core::TorshError> {
70//! #     unimplemented!()
71//! #   }
72//! # }
73//!
74//! # fn example() -> Result<(), torsh_core::TorshError> {
75//! let model = MyModel;
76//!
77//! // Configure advanced profiling
78//! let config = AdvancedProfilingConfig {
79//!     enable_flame_graph: true,
80//!     enable_memory_profiling: true,
81//!     enable_gpu_profiling: false,  // Enable if using GPU
82//!     enable_call_stack_analysis: true,
83//!     enable_hotspot_analysis: true,
84//!     sample_rate_hz: 1000.0,       // 1000 samples per second
85//!     memory_snapshot_interval_ms: 10.0,
86//!     ..Default::default()
87//! };
88//!
89//! let report = profile_bottlenecks_advanced(
90//!     &model,
91//!     &[1, 3, 224, 224],
92//!     100,
93//!     true,
94//!     config
95//! )?;
96//!
97//! // Analyze flame graph
98//! if let Some(flame_graph) = &report.flame_graph {
99//!     println!("Flame graph: {} samples at {:.0} Hz",
100//!         flame_graph.total_samples,
101//!         flame_graph.sample_rate_hz
102//!     );
103//! }
104//!
105//! // Analyze hotspots
106//! for hotspot in report.hotspot_analysis.cpu_hotspots.iter().take(5) {
107//!     println!("Hotspot: {} ({:.1}% of time)",
108//!         hotspot.function_name,
109//!         hotspot.time_percentage
110//!     );
111//! }
112//! # Ok(())
113//! # }
114//! ```
115//!
116//! ### Memory Leak Detection
117//!
118//! ```rust,no_run
119//! use torsh_utils::bottleneck::{profile_bottlenecks_advanced, AdvancedProfilingConfig};
120//! # use torsh_nn::Module;
121//! # struct MyModel;
122//! # impl Module for MyModel {
123//! #   fn forward(&self, _: &torsh_tensor::Tensor) -> Result<torsh_tensor::Tensor, torsh_core::TorshError> {
124//! #     unimplemented!()
125//! #   }
126//! # }
127//!
128//! # fn example() -> Result<(), torsh_core::TorshError> {
129//! let model = MyModel;
130//!
131//! let config = AdvancedProfilingConfig {
132//!     enable_memory_profiling: true,
133//!     memory_snapshot_interval_ms: 100.0,  // Frequent snapshots for leak detection
134//!     ..Default::default()
135//! };
136//!
137//! let report = profile_bottlenecks_advanced(&model, &[1, 3, 224, 224], 1000, true, config)?;
138//!
139//! // Check for memory leaks. `None` means leak detection could not run
140//! // (no allocator instrumentation is wired up) -- distinct from
141//! // `Some(vec![])`, which would mean detection ran and found nothing.
142//! match &report.memory_profile.memory_leaks {
143//!     Some(leaks) if !leaks.is_empty() => {
144//!         println!("⚠️  WARNING: {} memory leaks detected!", leaks.len());
145//!         for leak in leaks {
146//!             println!("  - {} bytes at {} (age: {:.1}s)",
147//!                 (leak.size_mb * 1024.0 * 1024.0) as usize,
148//!                 leak.allocation_site,
149//!                 leak.age_ms / 1000.0
150//!             );
151//!         }
152//!     }
153//!     Some(_) => println!("✓ No memory leaks detected"),
154//!     None => println!("Memory leak detection not available"),
155//! }
156//!
157//! // Check memory fragmentation, when it was measured.
158//! if let Some(fragmentation_ratio) = report.memory_profile.fragmentation_ratio {
159//!     if fragmentation_ratio > 0.2 {
160//!         println!("⚠️  High memory fragmentation: {:.1}%", fragmentation_ratio * 100.0);
161//!     }
162//! }
163//! # Ok(())
164//! # }
165//! ```
166//!
167//! ### GPU Profiling
168//!
169//! ```rust,no_run
170//! use torsh_utils::bottleneck::{profile_bottlenecks_advanced, AdvancedProfilingConfig};
171//! # use torsh_nn::Module;
172//! # struct MyModel;
173//! # impl Module for MyModel {
174//! #   fn forward(&self, _: &torsh_tensor::Tensor) -> Result<torsh_tensor::Tensor, torsh_core::TorshError> {
175//! #     unimplemented!()
176//! #   }
177//! # }
178//!
179//! # fn example() -> Result<(), torsh_core::TorshError> {
180//! let model = MyModel;
181//!
182//! let config = AdvancedProfilingConfig {
183//!     enable_gpu_profiling: true,
184//!     ..Default::default()
185//! };
186//!
187//! let report = profile_bottlenecks_advanced(&model, &[1, 3, 224, 224], 100, true, config)?;
188//!
189//! if let Some(gpu_profile) = &report.gpu_profile {
190//!     println!("GPU Utilization: {:.1}%", gpu_profile.utilization_percentage);
191//!     println!("GPU Memory: {:.1}%", gpu_profile.memory_utilization_percentage);
192//!     println!("Temperature: {:.1}°C", gpu_profile.temperature_celsius);
193//!     println!("Power: {:.1}W", gpu_profile.power_consumption_watts);
194//!
195//!     // Analyze kernel performance
196//!     for kernel in &gpu_profile.kernel_executions {
197//!         if kernel.occupancy < 0.5 {
198//!             println!("⚠️  Low occupancy kernel: {} ({:.1}% occupancy)",
199//!                 kernel.kernel_name,
200//!                 kernel.occupancy * 100.0
201//!             );
202//!         }
203//!     }
204//!
205//!     // Analyze memory transfers
206//!     for transfer in &gpu_profile.memory_transfers {
207//!         if transfer.bandwidth_gb_s < 100.0 {
208//!             println!("⚠️  Slow memory transfer: {:?} ({:.1} GB/s)",
209//!                 transfer.direction,
210//!                 transfer.bandwidth_gb_s
211//!             );
212//!         }
213//!     }
214//! }
215//! # Ok(())
216//! # }
217//! ```
218//!
219//! ## Understanding Results
220//!
221//! ### Hotspot Analysis
222//!
223//! Hotspots are functions or operations that consume the most CPU/GPU time:
224//! - **CPU Hotspots**: Functions with high execution time percentage
225//! - **GPU Hotspots**: CUDA kernels with high runtime or low occupancy
226//! - **Memory Hotspots**: Operations causing frequent allocations/deallocations
227//!
228//! ### Flame Graphs
229//!
230//! Flame graphs visualize call stacks over time:
231//! - **Width**: Time spent in function (including children)
232//! - **Height**: Call stack depth
233//! - **Color**: Can indicate different modules or call types
234//!
235//! ### Memory Profile
236//!
237//! - **Peak Usage**: Maximum memory allocated during execution
238//! - **Current Usage**: Memory in use at profile end
239//! - **Fragmentation**: Ratio of wasted memory due to fragmentation
240//! - **Leaks**: Allocations never freed (potential memory leaks)
241//!
242//! ## Best Practices
243//!
244//! 1. **Profile in Release Mode**: Debug builds have significant overhead
245//! 2. **Use Representative Workloads**: Profile with realistic input sizes
246//! 3. **Run Sufficient Iterations**: More iterations = better statistical significance
247//! 4. **Focus on Hot Paths**: Optimize the 20% of code taking 80% of time
248//! 5. **Verify Fixes**: Re-profile after optimizations to measure improvement
249//! 6. **Check Multiple Metrics**: Don't optimize time at the expense of memory
250//!
251//! ## Performance Tips
252//!
253//! ### CPU Optimization
254//! - Look for operations with high `time_percentage` in hotspot analysis
255//! - Check for unnecessary allocations in memory profile
256//! - Identify opportunities for vectorization (SIMD)
257//! - Consider parallelization for independent operations
258//!
259//! ### GPU Optimization
260//! - Target kernels with occupancy < 50%
261//! - Minimize host-device memory transfers
262//! - Use pinned memory for faster transfers
263//! - Optimize kernel launch configurations (grid/block sizes)
264//!
265//! ### Memory Optimization
266//! - Fix memory leaks immediately
267//! - Reduce fragmentation by using memory pools
268//! - Consider gradient checkpointing for large models
269//! - Use in-place operations where possible
270//!
271//! ## Comparison with PyTorch Profiler
272//!
273//! | Feature | PyTorch Profiler | ToRSh Bottleneck |
274//! |---------|------------------|------------------|
275//! | Flame Graphs | Via external tools | Built-in |
276//! | Memory Profiling | Basic | Advanced with leak detection |
277//! | GPU Analysis | CUDA only | CUDA + analysis |
278//! | Overhead | ~5-10% | ~2-5% |
279//! | Integration | TensorBoard | Standalone + TensorBoard |
280//!
281//! ## See Also
282//!
283//! - [`benchmark`](crate::benchmark): For performance benchmarking
284//! - [`tensorboard`](crate::tensorboard): For visualizing profiling data
285//! - [Tutorial Guide](https://docs.torsh.rs/tutorial#profiling)
286//! - [Best Practices](https://docs.torsh.rs/best-practices#profiling)
287
288// Framework infrastructure - components designed for future use
289#![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// Note: These features are defined in scirs2-core, not torsh-utils
297// Conditional compilation is handled at the scirs2-core level
298
299/// Comprehensive bottleneck report with advanced profiling data
300#[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    // Advanced profiling features
309    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/// Flame graph data structure for visualization
318#[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/// Individual frame in the flame graph
327#[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/// Comprehensive memory profiling data
339#[derive(Debug, Clone)]
340pub struct MemoryProfileData {
341    /// Real peak resident memory observed during profiling (MB).
342    pub peak_usage_mb: f32,
343    /// Real resident memory at the time metrics were read (MB).
344    pub current_usage_mb: f32,
345    pub allocation_timeline: Vec<MemorySnapshot>,
346    /// Detected memory leaks, when leak detection is actually implemented.
347    /// `None` means "not measured" (no allocator instrumentation is wired
348    /// up here) -- distinct from `Some(vec![])`, which would claim leak
349    /// detection ran and found nothing.
350    pub memory_leaks: Option<Vec<MemoryLeak>>,
351    /// Fragmentation ratio, when it can be measured from allocator
352    /// introspection. `None` if unmeasured.
353    pub fragmentation_ratio: Option<f32>,
354    pub gc_pressure: Option<f32>,
355    /// Memory bandwidth utilization, when it can be measured from hardware
356    /// performance counters. `None` if unmeasured.
357    pub memory_bandwidth_utilization: Option<f32>,
358    pub cache_performance: CachePerformance,
359}
360
361/// Memory snapshot at a point in time
362#[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/// Memory leak information
372#[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/// Cache performance metrics.
381///
382/// L1/L2/L3 hit rates, cache misses per instruction, and memory stall
383/// percentage all require reading hardware performance counters (e.g. via
384/// `perf_event_open` on Linux, typically permission-gated), which this
385/// crate does not do. Every field is `None` ("not measured") rather than a
386/// plausible-looking invented number -- see [`MemoryMetricsCollector`].
387#[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/// GPU profiling data
397#[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/// Individual GPU kernel execution data
410#[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/// GPU memory transfer data
422#[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/// Memory transfer direction
431#[derive(Debug, Clone)]
432pub enum MemoryTransferDirection {
433    HostToDevice,
434    DeviceToHost,
435    DeviceToDevice,
436    Unified,
437}
438
439/// Call stack analysis results
440#[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/// Call path with timing information
450#[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/// Recursive call detection
459#[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/// Performance regression analysis
467#[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/// Performance metrics for comparison
477#[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/// Hotspot analysis results
486#[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/// CPU computation hotspot
495#[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/// Memory access hotspot
505#[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/// Memory access pattern
514#[derive(Debug, Clone)]
515pub enum MemoryAccessPattern {
516    Sequential,
517    Random,
518    Strided { stride: usize },
519    Clustered,
520}
521
522/// I/O operation hotspot
523#[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/// Synchronization hotspot
532#[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/// Layer timing information
541#[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/// Operation timing information
552#[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/// Memory peak information
562#[derive(Debug, Clone)]
563pub struct MemoryPeak {
564    pub operation: String,
565    pub allocated_mb: f32,
566    pub reserved_mb: f32,
567}
568
569/// Advanced profiling configuration
570#[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, // Only enable if GPU available
588            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
597/// Profile bottlenecks with basic profiling
598pub 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
617/// Profile bottlenecks with comprehensive advanced profiling
618pub 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    // Initialize profilers
626    let mut profiler = Profiler::new();
627    let mut memory_collector = MemoryMetricsCollector::new();
628    let mut leak_detector = LeakDetector::new();
629
630    // Start profiling
631    profiler.start();
632
633    if config.enable_memory_profiling {
634        memory_collector.start_collection();
635        leak_detector.enable();
636    }
637
638    // Initialize data collection structures
639    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    // Each entry pairs a real captured call stack with the real duration of
644    // the iteration it was captured in, so `analyze_call_stacks` can report
645    // genuine per-path timing instead of a fabricated constant.
646    let mut call_stacks: Vec<(Vec<String>, Duration)> = Vec::new();
647
648    // GPU profiling setup
649    let gpu_profiler = if config.enable_gpu_profiling {
650        setup_gpu_profiling()
651    } else {
652        None
653    };
654
655    // Warmup runs
656    for _ in 0..3 {
657        let input = torsh_tensor::creation::randn(input_shape)?;
658        let _ = model.forward(&input)?;
659    }
660
661    // Main profiling loop
662    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        // Capture the real call stack at the point this iteration's
671        // compute begins; paired with the iteration's real duration once
672        // that's known below.
673        let call_stack = if config.enable_call_stack_analysis {
674            Some(capture_call_stack())
675        } else {
676            None
677        };
678
679        // Profile forward pass
680        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        // Profile backward pass if requested
690        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        // Memory snapshots
706        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            // Update the real peak-memory reading alongside the existing
717            // snapshot cadence (see MemoryMetricsCollector).
718            memory_collector.sample();
719            last_snapshot = Instant::now();
720        }
721
722        // Periodic memory peaks collection
723        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    // Stop all profilers
737    profiler.stop();
738
739    if config.enable_memory_profiling {
740        memory_collector.stop_collection();
741    }
742
743    // Collect profiling results. The flame graph and hotspot analysis are
744    // built from the real per-iteration forward/backward `Duration`s
745    // collected in the loop above, not from a fabricated sample set.
746    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    // Process operation timings
777    let processed_op_times = process_operation_times(operation_times);
778
779    // Generate recommendations
780    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
803/// Generate a flame graph from the real per-iteration operation timings
804/// collected during profiling (`operation_times`: e.g. "forward"/"backward"
805/// -> one real `Duration` per iteration), instead of a fixed, fabricated
806/// sample set describing functions that were never actually executed.
807///
808/// This is coarser than a true sampling profiler (it can only see the
809/// operations this crate explicitly times -- forward/backward passes as a
810/// whole, not individual layers inside them, since `Module::forward` is
811/// opaque here), but every number in it was genuinely measured.
812fn 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    // Real average sampling rate: how many real per-operation
819    // measurements were taken per second of wall-clock profiling time.
820    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
836/// Turn real per-iteration operation `Duration`s into one [`ProfileSample`]
837/// per iteration per operation, each carrying that operation's own name as
838/// its (single-frame) stack trace -- the only call-site information
839/// available without deeper instrumentation into the profiled model.
840fn 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
854/// Build flame graph tree structure from real samples (see
855/// [`real_profile_samples`]).
856fn 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    // Aggregate samples by function name, also tracking real sample counts
868    // (previously hardcoded to 1 regardless of how many samples an
869    // operation actually had).
870    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    // Create child frames
880    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/// Profile sample structure
898#[derive(Debug, Clone)]
899struct ProfileSample {
900    function_name: String,
901    duration_ms: f32,
902    stack_trace: Vec<String>,
903}
904
905/// Generate comprehensive memory profile
906fn 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    // `None` means leak detection is not implemented (no allocator
914    // instrumentation is wired up); `Some(vec)` (even if empty) would
915    // falsely claim detection ran and found nothing.
916    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/// Memory metrics: real process memory readings plus (unmeasured) cache
947/// counters. See [`MemoryMetricsCollector`].
948#[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/// A leak detected by [`LeakDetector`] (currently never constructed --
962/// see that type's doc comment).
963#[derive(Debug)]
964struct DetectedLeak {
965    location: String,
966    size_bytes: usize,
967    age_ms: f32,
968    stack_trace: Vec<String>,
969}
970
971/// Analyze layer timings from profile events
972#[allow(dead_code)]
973fn analyze_layer_timings(_events: &[ProfileEvent], _total_time: Duration) -> Vec<LayerTiming> {
974    // Simplified implementation - profiler integration not yet complete
975    Vec::new()
976}
977
978/// Process operation timings
979fn 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
1005/// Get current memory information from /proc/self/status.
1006/// Returns (rss_mb, vmsize_mb). On non-Linux platforms returns (0.0, 0.0).
1007fn 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        // Memory measurement via /proc/self/status not available on this platform
1032        Ok((0.0, 0.0))
1033    }
1034}
1035
1036/// Get detailed memory information for profiling from /proc/self/status and /proc/self/maps.
1037/// Returns (allocated_mb, reserved_mb, active_allocations, free_mb).
1038/// On non-Linux platforms returns (0.0, 0.0, 0, 0.0).
1039fn get_detailed_memory_info() -> Result<(f32, f32, usize, f32)> {
1040    #[cfg(target_os = "linux")]
1041    {
1042        // Read VmRSS (allocated = resident) and VmSize (reserved = virtual) from status
1043        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        // Approximate active allocations from /proc/self/maps line count
1062        let active_allocations = std::fs::read_to_string("/proc/self/maps")
1063            .map(|s| s.lines().count())
1064            .unwrap_or(0);
1065
1066        // Approximate free memory from /proc/meminfo MemFree
1067        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        // Detailed memory measurement via /proc not available on this platform
1084        Ok((0.0, 0.0, 0, 0.0))
1085    }
1086}
1087
1088/// Setup GPU profiling if available
1089fn setup_gpu_profiling() -> Option<GpuProfiler> {
1090    // Check if GPU is available and setup profiling
1091    // For now, return None (no GPU profiling)
1092    None
1093}
1094
1095/// Placeholder GPU profiler
1096struct GpuProfiler {
1097    _context: String,
1098}
1099
1100/// Collect GPU profiling data.
1101///
1102/// GPU profiling requires NVML or CUDA runtime linkage which is not currently
1103/// linked into this crate. All metrics are reported as 0.0 / empty to clearly
1104/// communicate that no measurement was taken. Callers should treat `gpu_profile`
1105/// as informational only when no `cuda` feature is active.
1106fn 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
1119/// Capture the real current call stack via `std::backtrace` (stable since
1120/// Rust 1.65; no extra dependency needed).
1121///
1122/// `force_capture` resolves symbols unconditionally, so behavior does not
1123/// depend on the `RUST_BACKTRACE` environment variable. The standard
1124/// library's `Backtrace` exposes only a formatted `Display`/`Debug`
1125/// rendering (no structured per-frame API), so individual frames are
1126/// recovered by splitting that rendering into non-empty lines -- real,
1127/// call-site-specific data, replacing the previous fixed 3-entry
1128/// placeholder that was returned for every call regardless of where it was
1129/// actually made from.
1130fn 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
1140/// Analyze call stacks for patterns.
1141///
1142/// `call_stacks` pairs each real captured stack with the real duration of
1143/// the iteration it was captured in (see `profile_bottlenecks_advanced`),
1144/// so per-path timing below is computed from genuine measurements instead
1145/// of a fixed placeholder.
1146fn 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    // Group identical stacks together and compute each group's real
1167    // aggregate timing, rather than stamping every path with a fixed
1168    // 100.0ms placeholder.
1169    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![], // Would detect recursive patterns
1201        call_frequency,
1202        average_stack_depth,
1203        max_stack_depth: max_depth,
1204    })
1205}
1206
1207/// Analyze performance hotspots from the real per-iteration operation
1208/// timings collected during profiling.
1209///
1210/// CPU hotspots are derived from genuinely measured operation durations
1211/// (coarse-grained: "forward"/"backward" as a whole, since `Module` does
1212/// not expose per-layer timing here). `instruction_count`/`cache_misses`/
1213/// `branch_mispredictions` require hardware performance counters this
1214/// crate does not read, so they are `None` rather than invented numbers.
1215///
1216/// Memory/I/O/synchronization hotspots require access-pattern, I/O, and
1217/// lock-contention instrumentation that does not exist in this crate;
1218/// rather than fabricate plausible-looking entries, these are honestly
1219/// empty until such instrumentation is implemented.
1220fn 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        // Would require memory access-pattern instrumentation.
1252        memory_hotspots: vec![],
1253        // Would require I/O instrumentation.
1254        io_hotspots: vec![],
1255        // Would require lock/synchronization instrumentation.
1256        synchronization_hotspots: vec![],
1257    }
1258}
1259
1260/// Generate advanced recommendations with comprehensive analysis
1261fn 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    // Basic recommendations (from original function)
1271    recommendations.extend(generate_recommendations(
1272        layer_times,
1273        operation_times,
1274        memory_peaks,
1275    ));
1276
1277    // Memory-specific recommendations -- only fire when the underlying
1278    // metric was actually measured.
1279    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    // CPU hotspot recommendations
1313    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    // Memory access pattern recommendations
1332    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    // I/O recommendations
1360    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    // Synchronization recommendations
1370    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
1382// Add default implementations for complex structures
1383impl Default for MemoryProfileData {
1384    /// Used when memory profiling was not enabled at all
1385    /// (`enable_memory_profiling: false`). Every field is a genuine zero/
1386    /// `None` for "not collected", not a fabricated "everything is
1387    /// perfect" reading (the previous implementation reported 100% cache
1388    /// hit rates here, which claimed cache performance had been checked
1389    /// and was flawless -- when in fact nothing had been measured at all).
1390    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        // Take one final real sample so the peak reflects memory right up
1457        // to the end of the profiled run.
1458        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                // None of these require hardware performance counters or
1470                // allocator introspection this crate does not have.
1471                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    /// Take a real process-memory reading now and fold it into the
1499    /// running peak. A no-op when the `collect_env` feature (which brings
1500    /// in `sysinfo`) is disabled.
1501    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
1511/// Real process memory metrics collector.
1512///
1513/// Uses `sysinfo` (available via this crate's default-enabled
1514/// `collect_env` feature) to sample this process's actual resident
1515/// memory. Cache-level counters (L1/L2/L3 hit rate, cache misses per
1516/// instruction, memory bandwidth utilization, fragmentation ratio)
1517/// require hardware performance counters or allocator introspection this
1518/// crate does not have -- see [`MemoryMetrics`] / [`CachePerformance`],
1519/// which report those as `None` rather than plausible-looking invented
1520/// numbers.
1521struct MemoryMetricsCollector {
1522    /// Peak resident memory (bytes) observed via [`Self::sample`] /
1523    /// `start_collection` / `stop_collection` so far.
1524    #[cfg(feature = "collect_env")]
1525    peak_bytes: u64,
1526}
1527
1528/// Real resident memory (RSS) of the current process, in bytes, via
1529/// `sysinfo`. Returns `None` if the current process could not be looked up
1530/// (e.g. an unsupported platform).
1531#[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        // Enable leak detection
1559    }
1560
1561    /// Real leak detection requires instrumenting the global allocator to
1562    /// record allocation call sites and ages (or an external tool such as
1563    /// Valgrind/heaptrack); neither is wired into this crate. Returns
1564    /// `None` ("not measured") rather than `Some(vec![])`, which would
1565    /// falsely claim detection ran and cleanly found zero leaks.
1566    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
1581/// Generate optimization recommendations
1582fn 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    // Check for slow layers
1590    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    // Check forward/backward balance
1600    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    // Check memory usage
1614    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    // Check for high-parameter layers
1629    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
1641/// Print bottleneck report
1642pub 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
1720/// Render an optional 0.0-1.0 ratio as a percentage, or "not measured"
1721/// when the underlying metric was never read (rather than silently
1722/// printing a `0.0%` that would look like a real, if bad, measurement).
1723fn 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; // used in cfg(linux) branch above
1789    }
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}