Skip to main content

trustformers_debug/
advanced_gpu_profiler.rs

1//! Advanced GPU profiling and kernel optimization tools
2//!
3//! This module provides comprehensive GPU memory analysis, kernel optimization
4//! suggestions, and advanced profiling capabilities for CUDA/ROCm/OpenCL kernels.
5// reason: debug/profiling scaffolding — structs are constructed and their fields/methods
6// are retained for the data model, serialization completeness, and future consumers that
7// do not yet read every member. Consolidated from many item-level #[allow(dead_code)].
8#![allow(dead_code)]
9
10use anyhow::Result;
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use std::collections::{HashMap, VecDeque};
14use std::time::{Duration, SystemTime};
15use uuid::Uuid;
16
17/// Advanced GPU memory profiler with fragmentation analysis
18#[derive(Debug)]
19pub struct AdvancedGpuMemoryProfiler {
20    device_count: i32,
21    memory_pools: HashMap<i32, GpuMemoryPool>,
22    memory_allocations: HashMap<Uuid, GpuMemoryAllocation>,
23    fragmentation_history: VecDeque<MemoryFragmentationSnapshot>,
24    bandwidth_monitors: HashMap<i32, GpuBandwidthMonitor>,
25    memory_pressure_monitor: MemoryPressureMonitor,
26    cross_device_transfers: Vec<CrossDeviceTransfer>,
27    /// Real host-OS telemetry handle (`sysinfo`), used ONLY to compute
28    /// [`MemoryPressureSnapshot::swap_activity`] -- see that field's doc
29    /// comment for why this is host-wide rather than per-GPU.
30    system_info: sysinfo::System,
31    /// Previous real `used_swap()` reading, so `swap_activity` can report
32    /// a genuine delta instead of a single instantaneous level. `None`
33    /// until the first [`Self::update_memory_pressure`] call.
34    last_used_swap_bytes: Option<u64>,
35}
36
37/// GPU memory allocation with detailed tracking
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct GpuMemoryAllocation {
40    pub allocation_id: Uuid,
41    pub device_id: i32,
42    pub size_bytes: usize,
43    pub alignment: usize,
44    pub memory_type: GpuMemoryType,
45    pub allocation_context: AllocationContext,
46    pub timestamp: SystemTime,
47    pub freed: bool,
48    pub free_timestamp: Option<SystemTime>,
49    pub access_pattern: MemoryAccessPattern,
50    pub usage_statistics: MemoryUsageStats,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum GpuMemoryType {
55    Global,
56    Shared,
57    Constant,
58    Texture,
59    Local,
60    Unified,
61    Pinned,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct AllocationContext {
66    pub kernel_name: Option<String>,
67    pub tensor_name: Option<String>,
68    pub layer_name: Option<String>,
69    pub allocation_source: AllocationSource,
70    pub stack_trace: Vec<String>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub enum AllocationSource {
75    TensorCreation,
76    KernelLaunch,
77    IntermediateBuffer,
78    GradientBuffer,
79    WeightBuffer,
80    ActivationBuffer,
81    CacheBuffer,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct MemoryAccessPattern {
86    pub access_frequency: f64,
87    pub read_ratio: f64,
88    pub write_ratio: f64,
89    pub sequential_access_ratio: f64,
90    pub random_access_ratio: f64,
91    pub coalesced_access_ratio: f64,
92    pub cache_hit_rate: f64,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, Default)]
96pub struct MemoryUsageStats {
97    pub total_accesses: u64,
98    pub bytes_read: u64,
99    pub bytes_written: u64,
100    pub lifetime_duration: Option<Duration>,
101    pub peak_concurrent_usage: usize,
102}
103
104/// Memory fragmentation analysis
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct MemoryFragmentationSnapshot {
107    pub timestamp: DateTime<Utc>,
108    pub device_id: i32,
109    /// Capacity this pool was configured with -- see `GpuMemoryPool`'s
110    /// own doc comment for how that number is obtained (this crate has no
111    /// pure-Rust GPU memory query API).
112    pub total_memory: usize,
113    /// Real remaining capacity: `total_memory` minus the real sum of
114    /// currently-outstanding allocations tracked via
115    /// [`AdvancedGpuMemoryProfiler::track_allocation`] /
116    /// [`AdvancedGpuMemoryProfiler::track_deallocation`] -- genuine
117    /// bookkeeping from real call arguments, not fabricated.
118    pub free_memory: usize,
119    /// Size of the largest contiguous free block, when measurable. This
120    /// pool tracks only a running free-BYTE COUNT, never the placement of
121    /// individual allocations in address space, so it has no way to know
122    /// whether that free capacity is one block or many small ones --
123    /// `None`, never a claim that all free memory forms one contiguous
124    /// block (the previous behavior).
125    pub largest_free_block: Option<usize>,
126    /// `None` for the same reason as [`Self::largest_free_block`]: real
127    /// fragmentation is a function of block placement and allocator
128    /// policy, neither of which this crate tracks or simulates.
129    pub fragmentation_ratio: Option<f64>,
130    /// Per-block free-space sizes, when known. `None` -- not an empty
131    /// `Vec`, which would misleadingly read as "zero free blocks exist"
132    /// -- see [`Self::largest_free_block`].
133    pub free_block_distribution: Option<Vec<usize>>,
134    /// `None` for the same reason as [`Self::fragmentation_ratio`].
135    pub external_fragmentation: Option<f64>,
136    /// `None` for the same reason as [`Self::fragmentation_ratio`].
137    pub internal_fragmentation: Option<f64>,
138}
139
140/// GPU bandwidth monitoring
141#[derive(Debug)]
142pub struct GpuBandwidthMonitor {
143    device_id: i32,
144    bandwidth_samples: VecDeque<BandwidthSample>,
145    theoretical_bandwidth: f64, // GB/s
146    peak_observed_bandwidth: f64,
147    sustained_bandwidth_history: Vec<SustainedBandwidthMeasurement>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct BandwidthSample {
152    pub timestamp: SystemTime,
153    pub memory_type: GpuMemoryType,
154    pub operation_type: MemoryOperationType,
155    pub bytes_transferred: usize,
156    pub duration: Duration,
157    pub achieved_bandwidth_gb_s: f64,
158    pub efficiency_percentage: f64,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub enum MemoryOperationType {
163    HostToDevice,
164    DeviceToHost,
165    DeviceToDevice,
166    KernelMemoryAccess,
167    PeerToPeer,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct SustainedBandwidthMeasurement {
172    pub duration: Duration,
173    pub avg_bandwidth_gb_s: f64,
174    pub min_bandwidth_gb_s: f64,
175    pub max_bandwidth_gb_s: f64,
176    pub bandwidth_variability: f64,
177}
178
179/// Memory pressure monitoring
180#[derive(Debug)]
181pub struct MemoryPressureMonitor {
182    pressure_history: VecDeque<MemoryPressureSnapshot>,
183    pressure_thresholds: MemoryPressureThresholds,
184    auto_optimization_enabled: bool,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct MemoryPressureSnapshot {
189    pub timestamp: DateTime<Utc>,
190    pub device_id: i32,
191    pub pressure_level: MemoryPressureLevel,
192    pub available_memory_ratio: f64,
193    pub allocation_rate: f64, // allocations per second
194    pub deallocation_rate: f64,
195    /// `None`: Rust has no garbage collector, and this crate has no hook
196    /// into any framework-level GC, so there is no real signal to report
197    /// here. Never a fabricated `0.0`.
198    pub gc_pressure: Option<f64>,
199    /// Real change in HOST OS swap usage (bytes, signed -- positive means
200    /// swap grew) since the previous snapshot, read via `sysinfo`
201    /// (already a workspace dependency; see
202    /// `AdvancedGpuMemoryProfiler::last_used_swap_bytes`). `None` only
203    /// for the very first snapshot, when there is no previous reading to
204    /// diff against. This is deliberately HOST-wide, not
205    /// `device_id`-scoped: no GPU vendor exposes a per-device "swap"
206    /// concept to userspace, so a genuinely per-GPU number does not
207    /// exist to measure. A single instantaneous reading would be a
208    /// LEVEL, not "activity" -- this is a real delta, not a level
209    /// wearing that name.
210    pub swap_activity: Option<f64>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub enum MemoryPressureLevel {
215    Low,
216    Medium,
217    High,
218    Critical,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct MemoryPressureThresholds {
223    pub medium_threshold: f64, // 0.7 = 70% memory usage triggers medium pressure
224    pub high_threshold: f64,   // 0.85 = 85% memory usage triggers high pressure
225    pub critical_threshold: f64, // 0.95 = 95% memory usage triggers critical pressure
226}
227
228/// Cross-device memory transfer tracking
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct CrossDeviceTransfer {
231    pub transfer_id: Uuid,
232    pub source_device: i32,
233    pub target_device: i32,
234    pub bytes_transferred: usize,
235    pub transfer_type: CrossDeviceTransferType,
236    pub duration: Duration,
237    pub bandwidth_achieved: f64,
238    /// Whether this transfer used peer-to-peer DMA. `None` when unknown:
239    /// this crate has no pure-Rust API to query real GPU P2P capability
240    /// (see `AdvancedGpuMemoryProfiler::detect_p2p_capability`) -- never
241    /// a guessed `true`.
242    pub p2p_enabled: Option<bool>,
243    pub timestamp: SystemTime,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub enum CrossDeviceTransferType {
248    DirectMemoryAccess,
249    PeerToPeer,
250    HostBounced,
251    NvLink,
252    Infinity,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct KernelExecutionProfile {
257    pub kernel_name: String,
258    pub execution_count: usize,
259    pub total_execution_time: Duration,
260    pub avg_execution_time: Duration,
261    pub min_execution_time: Duration,
262    pub max_execution_time: Duration,
263    pub grid_sizes: Vec<(u32, u32, u32)>,
264    pub block_sizes: Vec<(u32, u32, u32)>,
265    pub shared_memory_usage: Vec<usize>,
266    pub register_usage: Vec<u32>,
267    pub occupancy_measurements: Vec<f64>,
268    pub compute_utilization: Vec<f64>,
269    pub memory_bandwidth_utilization: Vec<f64>,
270    pub warp_efficiency: Vec<f64>,
271    pub memory_efficiency: Vec<f64>,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct KernelOptimization {
276    pub optimization_type: OptimizationType,
277    pub current_value: OptimizationValue,
278    pub suggested_value: OptimizationValue,
279    pub expected_improvement: ExpectedImprovement,
280    pub confidence: f64,
281    pub explanation: String,
282    pub implementation_difficulty: ImplementationDifficulty,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub enum OptimizationType {
287    BlockSize,
288    GridSize,
289    SharedMemory,
290    RegisterOptimization,
291    MemoryCoalescing,
292    WarpDivergence,
293    KernelFusion,
294    MemoryLayoutOptimization,
295    ComputeIntensityBalance,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub enum OptimizationValue {
300    IntegerValue(u32),
301    FloatValue(f64),
302    TupleValue((u32, u32, u32)),
303    LayoutPattern(String),
304    BooleanValue(bool),
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct ExpectedImprovement {
309    pub performance_gain_percentage: f64,
310    pub memory_usage_reduction_percentage: f64,
311    pub energy_efficiency_improvement: f64,
312    pub scalability_improvement: f64,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub enum ImplementationDifficulty {
317    Trivial,
318    Easy,
319    Moderate,
320    Difficult,
321    Expert,
322}
323
324/// Launch configuration analysis
325#[derive(Debug)]
326pub struct LaunchConfigAnalyzer {
327    optimal_configs: HashMap<String, OptimalLaunchConfig>,
328    config_performance_history: HashMap<String, Vec<ConfigPerformanceMeasurement>>,
329    autotuning_enabled: bool,
330    search_space_cache: HashMap<String, LaunchConfigSearchSpace>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct LaunchConfigSearchSpace {
335    pub kernel_name: String,
336    pub min_block_size: (u32, u32, u32),
337    pub max_block_size: (u32, u32, u32),
338    pub min_grid_size: (u32, u32, u32),
339    pub max_grid_size: (u32, u32, u32),
340    pub min_shared_memory: usize,
341    pub max_shared_memory: usize,
342    pub search_constraints: Vec<LaunchConstraint>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct OptimalLaunchConfig {
347    pub kernel_name: String,
348    pub optimal_block_size: (u32, u32, u32),
349    pub optimal_grid_size: (u32, u32, u32),
350    pub optimal_shared_memory: usize,
351    pub expected_occupancy: f64,
352    pub expected_performance: f64,
353    pub constraints: Vec<LaunchConstraint>,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ConfigPerformanceMeasurement {
358    pub block_size: (u32, u32, u32),
359    pub grid_size: (u32, u32, u32),
360    pub shared_memory: usize,
361    pub achieved_occupancy: f64,
362    pub execution_time: Duration,
363    pub memory_bandwidth: f64,
364    pub compute_utilization: f64,
365    pub timestamp: SystemTime,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub enum LaunchConstraint {
370    MaxSharedMemory(usize),
371    MaxRegisters(u32),
372    MinOccupancy(f64),
373    WorkgroupSizeLimit(u32),
374    MemoryBandwidthLimit(f64),
375}
376
377/// Memory access pattern analysis
378#[derive(Debug)]
379pub struct MemoryAccessAnalyzer {
380    access_patterns: HashMap<String, MemoryAccessAnalysis>,
381    coalescing_analysis: HashMap<String, CoalescingAnalysis>,
382    cache_performance: HashMap<String, CachePerformanceAnalysis>,
383    stride_analysis: HashMap<String, StrideAnalysisResult>,
384    bank_conflict_analyzer: BankConflictAnalyzer,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct StrideAnalysisResult {
389    pub kernel_name: String,
390    pub average_stride: f64,
391    pub stride_pattern: StridePattern,
392    pub optimization_potential: f64,
393    pub recommended_changes: Vec<String>,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize)]
397pub enum StridePattern {
398    Sequential,
399    Strided(i32),
400    Random,
401    Broadcast,
402}
403
404#[derive(Debug)]
405pub struct BankConflictAnalyzer {
406    conflict_patterns: HashMap<String, BankConflictPattern>,
407    resolution_strategies: HashMap<String, Vec<ConflictResolutionStrategy>>,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct BankConflictPattern {
412    pub kernel_name: String,
413    pub conflicts_detected: usize,
414    pub conflict_severity: ConflictSeverity,
415    pub affected_warps: Vec<u32>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub enum ConflictSeverity {
420    Low,
421    Medium,
422    High,
423    Critical,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct ConflictResolutionStrategy {
428    pub strategy_type: ResolutionStrategyType,
429    pub description: String,
430    pub expected_improvement: f64,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub enum ResolutionStrategyType {
435    DataPadding,
436    AccessReordering,
437    SharedMemoryBanking,
438    AlgorithmicChange,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct MemoryAccessAnalysis {
443    pub kernel_name: String,
444    pub total_memory_transactions: u64,
445    pub coalesced_transactions: u64,
446    pub uncoalesced_transactions: u64,
447    pub stride_patterns: Vec<StridePattern>,
448    pub access_locality: AccessLocalityMetrics,
449    pub bank_conflicts: u64,
450    pub cache_line_utilization: f64,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct DetectedStride {
455    pub stride_size: usize,
456    pub frequency: u64,
457    pub efficiency_impact: f64,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct AccessLocalityMetrics {
462    pub temporal_locality_score: f64,
463    pub spatial_locality_score: f64,
464    pub working_set_size: usize,
465    pub reuse_distance_avg: f64,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct CoalescingAnalysis {
470    pub kernel_name: String,
471    pub coalescing_efficiency: f64,
472    pub uncoalesced_regions: Vec<UncoalescedRegion>,
473    pub suggested_improvements: Vec<CoalescingImprovement>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct UncoalescedRegion {
478    pub memory_region: String,
479    pub access_pattern: String,
480    pub efficiency_loss: f64,
481    pub fix_difficulty: ImplementationDifficulty,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct CoalescingImprovement {
486    pub improvement_type: CoalescingImprovementType,
487    pub description: String,
488    pub expected_speedup: f64,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
492pub enum CoalescingImprovementType {
493    DataLayoutReorganization,
494    AccessPatternOptimization,
495    SharedMemoryBuffering,
496    VectorizedAccess,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct CachePerformanceAnalysis {
501    pub kernel_name: String,
502    pub l1_cache_hit_rate: f64,
503    pub l2_cache_hit_rate: f64,
504    pub texture_cache_hit_rate: f64,
505    pub shared_memory_bank_conflicts: u64,
506    pub cache_thrashing_detected: bool,
507    pub recommended_cache_optimizations: Vec<CacheOptimization>,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct CacheOptimization {
512    pub optimization_type: CacheOptimizationType,
513    pub description: String,
514    pub expected_improvement: f64,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub enum CacheOptimizationType {
519    DataPrefetching,
520    CacheBlockingStrategy,
521    SharedMemoryUsage,
522    TextureMemoryUsage,
523    ConstantMemoryUsage,
524}
525
526/// Compute utilization analysis
527#[derive(Debug)]
528pub struct ComputeUtilizationAnalyzer {
529    utilization_profiles: HashMap<String, ComputeUtilizationProfile>,
530    bottleneck_analysis: HashMap<String, ComputeBottleneckAnalysis>,
531    arithmetic_intensity_analyzer: ArithmeticIntensityAnalyzer,
532    resource_balancer: ResourceBalancer,
533}
534
535#[derive(Debug)]
536pub struct ArithmeticIntensityAnalyzer {
537    intensity_profiles: HashMap<String, ArithmeticIntensityProfile>,
538    roofline_models: HashMap<i32, RooflineModel>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct ArithmeticIntensityProfile {
543    pub kernel_name: String,
544    pub arithmetic_intensity: f64,
545    pub operations_per_byte: f64,
546    pub peak_performance_percentage: f64,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct RooflineModel {
551    pub device_id: i32,
552    pub peak_compute_flops: f64,
553    pub peak_memory_bandwidth: f64,
554    pub ridge_point: f64,
555}
556
557#[derive(Debug)]
558pub struct ResourceBalancer {
559    resource_profiles: HashMap<String, ResourceProfile>,
560    balancing_strategies: HashMap<String, BalancingStrategy>,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize)]
564pub struct ResourceProfile {
565    pub kernel_name: String,
566    pub register_usage: f64,
567    pub shared_memory_usage: f64,
568    pub occupancy: f64,
569    pub limiting_factor: LimitingFactor,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
573pub enum LimitingFactor {
574    Registers,
575    SharedMemory,
576    Blocks,
577    Warps,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct BalancingStrategy {
582    pub strategy_name: String,
583    pub description: String,
584    pub expected_improvement: f64,
585    pub trade_offs: Vec<String>,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct ComputeUtilizationProfile {
590    pub kernel_name: String,
591    pub arithmetic_intensity: f64,
592    pub compute_throughput: f64,
593    pub memory_throughput: f64,
594    pub compute_to_memory_ratio: f64,
595    pub warp_execution_efficiency: f64,
596    pub instruction_mix: InstructionMixAnalysis,
597    pub resource_utilization: ResourceUtilizationMetrics,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct InstructionMixAnalysis {
602    pub integer_ops_percentage: f64,
603    pub float_ops_percentage: f64,
604    pub double_ops_percentage: f64,
605    pub special_function_ops_percentage: f64,
606    pub memory_ops_percentage: f64,
607    pub control_flow_ops_percentage: f64,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
611pub struct ResourceUtilizationMetrics {
612    pub register_utilization: f64,
613    pub shared_memory_utilization: f64,
614    pub constant_memory_utilization: f64,
615    pub texture_cache_utilization: f64,
616    pub compute_unit_utilization: f64,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
620pub struct ComputeBottleneckAnalysis {
621    pub kernel_name: String,
622    pub primary_bottleneck: ComputeBottleneckType,
623    pub bottleneck_severity: f64,
624    pub contributing_factors: Vec<BottleneckFactor>,
625    pub optimization_opportunities: Vec<ComputeOptimizationOpportunity>,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize)]
629pub enum ComputeBottleneckType {
630    MemoryBandwidth,
631    ComputeThroughput,
632    Latency,
633    Occupancy,
634    WarpDivergence,
635    SynchronizationOverhead,
636}
637
638#[derive(Debug, Clone, Serialize, Deserialize)]
639pub struct BottleneckFactor {
640    pub factor_type: String,
641    pub impact_percentage: f64,
642    pub description: String,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct ComputeOptimizationOpportunity {
647    pub opportunity_type: ComputeOptimizationType,
648    pub description: String,
649    pub expected_speedup: f64,
650    pub implementation_effort: ImplementationDifficulty,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
654pub enum ComputeOptimizationType {
655    KernelFusion,
656    MemoryOptimization,
657    ParallelismIncrease,
658    AlgorithmicImprovement,
659    ResourceBalancing,
660}
661
662impl AdvancedGpuMemoryProfiler {
663    pub fn new(device_count: i32) -> Result<Self> {
664        let mut memory_pools = HashMap::new();
665        let mut bandwidth_monitors = HashMap::new();
666
667        for device_id in 0..device_count {
668            memory_pools.insert(device_id, GpuMemoryPool::new(device_id)?);
669            bandwidth_monitors.insert(device_id, GpuBandwidthMonitor::new(device_id)?);
670        }
671
672        Ok(Self {
673            device_count,
674            memory_pools,
675            memory_allocations: HashMap::new(),
676            fragmentation_history: VecDeque::with_capacity(1000),
677            bandwidth_monitors,
678            memory_pressure_monitor: MemoryPressureMonitor::new(),
679            cross_device_transfers: Vec::new(),
680            system_info: sysinfo::System::new(),
681            last_used_swap_bytes: None,
682        })
683    }
684
685    /// Track a GPU memory allocation with detailed context
686    pub fn track_allocation(
687        &mut self,
688        device_id: i32,
689        size_bytes: usize,
690        memory_type: GpuMemoryType,
691        context: AllocationContext,
692    ) -> Result<Uuid> {
693        let allocation_id = Uuid::new_v4();
694        let allocation = GpuMemoryAllocation {
695            allocation_id,
696            device_id,
697            size_bytes,
698            alignment: self.calculate_optimal_alignment(size_bytes),
699            memory_type,
700            allocation_context: context,
701            timestamp: SystemTime::now(),
702            freed: false,
703            free_timestamp: None,
704            access_pattern: MemoryAccessPattern::default(),
705            usage_statistics: MemoryUsageStats::default(),
706        };
707
708        // Update memory pool
709        if let Some(pool) = self.memory_pools.get_mut(&device_id) {
710            pool.allocate(size_bytes)?;
711        }
712
713        self.memory_allocations.insert(allocation_id, allocation);
714
715        // Check for memory pressure
716        self.update_memory_pressure(device_id);
717
718        Ok(allocation_id)
719    }
720
721    /// Track memory deallocation
722    pub fn track_deallocation(&mut self, allocation_id: Uuid) -> Result<()> {
723        let device_id = if let Some(allocation) = self.memory_allocations.get_mut(&allocation_id) {
724            allocation.freed = true;
725            allocation.free_timestamp = Some(SystemTime::now());
726
727            // Get the device_id and size_bytes before dropping the mutable reference
728            let device_id = allocation.device_id;
729            let size_bytes = allocation.size_bytes;
730
731            // Update memory pool
732            if let Some(pool) = self.memory_pools.get_mut(&device_id) {
733                pool.deallocate(size_bytes)?;
734            }
735
736            Some(device_id)
737        } else {
738            None
739        };
740
741        // Update memory pressure after dropping the mutable reference
742        if let Some(device_id) = device_id {
743            self.update_memory_pressure(device_id);
744        }
745
746        Ok(())
747    }
748
749    /// Analyze memory fragmentation across all devices
750    pub fn analyze_fragmentation(&mut self) -> Result<Vec<MemoryFragmentationSnapshot>> {
751        let mut snapshots = Vec::new();
752
753        for (&_device_id, pool) in &self.memory_pools {
754            let snapshot = pool.get_fragmentation_snapshot()?;
755            snapshots.push(snapshot.clone());
756
757            // Store in history
758            self.fragmentation_history.push_back(snapshot);
759            if self.fragmentation_history.len() > 1000 {
760                self.fragmentation_history.pop_front();
761            }
762        }
763
764        Ok(snapshots)
765    }
766
767    /// Monitor memory bandwidth utilization
768    pub fn record_bandwidth_sample(
769        &mut self,
770        device_id: i32,
771        sample: BandwidthSample,
772    ) -> Result<()> {
773        if let Some(monitor) = self.bandwidth_monitors.get_mut(&device_id) {
774            monitor.add_sample(sample)?;
775        }
776        Ok(())
777    }
778
779    /// Track cross-device memory transfer
780    pub fn track_cross_device_transfer(
781        &mut self,
782        source_device: i32,
783        target_device: i32,
784        bytes_transferred: usize,
785        transfer_type: CrossDeviceTransferType,
786        duration: Duration,
787    ) -> Result<Uuid> {
788        let transfer_id = Uuid::new_v4();
789        let bandwidth_achieved =
790            bytes_transferred as f64 / (1024.0 * 1024.0 * 1024.0) / duration.as_secs_f64();
791
792        let transfer = CrossDeviceTransfer {
793            transfer_id,
794            source_device,
795            target_device,
796            bytes_transferred,
797            transfer_type,
798            duration,
799            bandwidth_achieved,
800            p2p_enabled: self.detect_p2p_capability(source_device, target_device),
801            timestamp: SystemTime::now(),
802        };
803
804        self.cross_device_transfers.push(transfer);
805        Ok(transfer_id)
806    }
807
808    /// Get comprehensive memory analysis report
809    pub fn get_memory_analysis_report(&self) -> MemoryAnalysisReport {
810        let fragmentation_summary = self.analyze_fragmentation_trends();
811        let bandwidth_summary = self.analyze_bandwidth_utilization();
812        let pressure_summary = self.analyze_memory_pressure();
813        let allocation_summary = self.analyze_allocation_patterns();
814        let cross_device_summary = self.analyze_cross_device_transfers();
815
816        MemoryAnalysisReport {
817            fragmentation_summary,
818            bandwidth_summary,
819            pressure_summary,
820            allocation_summary,
821            cross_device_summary,
822            optimization_recommendations: self.generate_memory_optimization_recommendations(),
823        }
824    }
825
826    fn calculate_optimal_alignment(&self, size_bytes: usize) -> usize {
827        // Calculate optimal memory alignment for GPU access
828        if size_bytes >= 128 {
829            128 // Cache line alignment
830        } else if size_bytes >= 64 {
831            64
832        } else if size_bytes >= 32 {
833            32
834        } else {
835            16
836        }
837    }
838
839    fn update_memory_pressure(&mut self, device_id: i32) {
840        let Some((pressure_level, available_memory_ratio)) =
841            self.memory_pools.get(&device_id).map(|pool| {
842                (
843                    pool.calculate_pressure_level(),
844                    pool.get_available_memory_ratio(),
845                )
846            })
847        else {
848            return;
849        };
850        let allocation_rate = self.calculate_allocation_rate(device_id);
851        let deallocation_rate = self.calculate_deallocation_rate(device_id);
852        let swap_activity = self.compute_swap_activity_delta();
853
854        let pressure_snapshot = MemoryPressureSnapshot {
855            timestamp: Utc::now(),
856            device_id,
857            pressure_level,
858            available_memory_ratio,
859            allocation_rate,
860            deallocation_rate,
861            // Rust has no garbage collector and this crate has no hook
862            // into any framework-level GC -- see the field's own doc
863            // comment.
864            gc_pressure: None,
865            swap_activity,
866        };
867
868        self.memory_pressure_monitor.add_snapshot(pressure_snapshot);
869    }
870
871    /// Real change in HOST OS swap usage in bytes since the previous call
872    /// (positive = swap grew), via `sysinfo` (already a workspace
873    /// dependency -- same pattern as `realtime_dashboard.rs`). `None` only
874    /// on the very first call, when there is no previous reading yet. See
875    /// [`MemoryPressureSnapshot::swap_activity`] for the host-wide-not-
876    /// per-GPU caveat.
877    fn compute_swap_activity_delta(&mut self) -> Option<f64> {
878        self.system_info.refresh_memory();
879        let used = self.system_info.used_swap();
880        let delta = self.last_used_swap_bytes.map(|prev| used as f64 - prev as f64);
881        self.last_used_swap_bytes = Some(used);
882        delta
883    }
884
885    /// Whether `source`/`target` support peer-to-peer DMA, when knowable.
886    /// This crate has no pure-Rust GPU capability query API (a real one
887    /// would need vendor FFI -- CUDA/ROCm/NVML -- which the COOLJAPAN
888    /// pure-Rust policy keeps out of the default build), so there is
889    /// nothing to honestly detect here today: always `None`, never a
890    /// guessed `true`.
891    fn detect_p2p_capability(&self, _source: i32, _target: i32) -> Option<bool> {
892        None
893    }
894
895    fn calculate_allocation_rate(&self, device_id: i32) -> f64 {
896        // Calculate allocations per second for the device
897        let recent_allocations = self
898            .memory_allocations
899            .values()
900            .filter(|a| a.device_id == device_id)
901            .filter(|a| a.timestamp.elapsed().unwrap_or_default().as_secs() < 60)
902            .count();
903
904        recent_allocations as f64 / 60.0
905    }
906
907    fn calculate_deallocation_rate(&self, device_id: i32) -> f64 {
908        // Calculate deallocations per second for the device
909        let recent_deallocations = self
910            .memory_allocations
911            .values()
912            .filter(|a| a.device_id == device_id && a.freed)
913            .filter(|a| {
914                if let Some(free_time) = a.free_timestamp {
915                    free_time.elapsed().unwrap_or_default().as_secs() < 60
916                } else {
917                    false
918                }
919            })
920            .count();
921
922        recent_deallocations as f64 / 60.0
923    }
924
925    fn analyze_fragmentation_trends(&self) -> FragmentationSummary {
926        // Analyze fragmentation trends from history
927        FragmentationSummary::new(&self.fragmentation_history)
928    }
929
930    fn analyze_bandwidth_utilization(&self) -> BandwidthSummary {
931        BandwidthSummary::new(&self.bandwidth_monitors)
932    }
933
934    fn analyze_memory_pressure(&self) -> MemoryPressureSummary {
935        self.memory_pressure_monitor.get_summary()
936    }
937
938    fn analyze_allocation_patterns(&self) -> AllocationPatternSummary {
939        AllocationPatternSummary::new(&self.memory_allocations)
940    }
941
942    fn analyze_cross_device_transfers(&self) -> CrossDeviceTransferSummary {
943        CrossDeviceTransferSummary::new(&self.cross_device_transfers)
944    }
945
946    fn generate_memory_optimization_recommendations(
947        &self,
948    ) -> Vec<MemoryOptimizationRecommendation> {
949        let mut recommendations = Vec::new();
950
951        // Analyze fragmentation and suggest optimizations, when this
952        // pool's fragmentation was actually measurable for that snapshot
953        // -- see `MemoryFragmentationSnapshot::fragmentation_ratio`'s doc
954        // comment. No allocator/placement model exists in this crate
955        // today, so this loop is currently a no-op in practice; it is
956        // still real code, ready the moment a real ratio is ever
957        // populated, rather than fabricating one to keep it "working".
958        for snapshot in self.fragmentation_history.iter().take(10) {
959            let Some(ratio) = snapshot.fragmentation_ratio else {
960                continue;
961            };
962            if ratio > 0.3 {
963                recommendations.push(MemoryOptimizationRecommendation {
964                    recommendation_type: MemoryOptimizationType::DefragmentationStrategy,
965                    priority: OptimizationPriority::High,
966                    description: format!(
967                        "High fragmentation detected on device {}: {:.1}%",
968                        snapshot.device_id,
969                        ratio * 100.0
970                    ),
971                    expected_benefit: ExpectedBenefit {
972                        performance_improvement: 15.0,
973                        memory_efficiency_improvement: 25.0,
974                        implementation_effort: ImplementationDifficulty::Moderate,
975                    },
976                    implementation_steps: vec![
977                        "Implement memory pooling with fixed-size blocks".to_string(),
978                        "Add periodic defragmentation during idle periods".to_string(),
979                        "Consider memory compaction strategies".to_string(),
980                    ],
981                });
982            }
983        }
984
985        recommendations
986    }
987}
988
989// Helper structures for analysis reports
990
991#[derive(Debug, Clone, Serialize, Deserialize)]
992pub struct MemoryAnalysisReport {
993    pub fragmentation_summary: FragmentationSummary,
994    pub bandwidth_summary: BandwidthSummary,
995    pub pressure_summary: MemoryPressureSummary,
996    pub allocation_summary: AllocationPatternSummary,
997    pub cross_device_summary: CrossDeviceTransferSummary,
998    pub optimization_recommendations: Vec<MemoryOptimizationRecommendation>,
999}
1000
1001#[derive(Debug, Clone, Serialize, Deserialize)]
1002pub struct FragmentationSummary {
1003    /// Mean of the real (`Some`) fragmentation ratios recorded across the
1004    /// summarised history. `None` when none of that history carries a
1005    /// measured ratio -- see
1006    /// [`MemoryFragmentationSnapshot::fragmentation_ratio`]. Never a
1007    /// fabricated `0.1`.
1008    pub avg_fragmentation_ratio: Option<f64>,
1009    pub peak_fragmentation_ratio: Option<f64>,
1010    pub fragmentation_trend: FragmentationTrend,
1011    pub most_fragmented_device: Option<i32>,
1012}
1013
1014#[derive(Debug, Clone, Serialize, Deserialize)]
1015pub enum FragmentationTrend {
1016    Improving,
1017    Stable,
1018    Worsening,
1019}
1020
1021#[derive(Debug, Clone, Serialize, Deserialize)]
1022pub struct BandwidthSummary {
1023    /// A "utilization" ratio needs a real theoretical peak bandwidth to
1024    /// divide by; this crate has no pure-Rust GPU capability query for
1025    /// one (see [`GpuBandwidthMonitor`]'s `theoretical_bandwidth`, itself
1026    /// a documented assumption, not a measurement), so honestly `None`
1027    /// rather than a ratio against an invented denominator.
1028    pub avg_bandwidth_utilization: Option<f64>,
1029    /// Real maximum `achieved_bandwidth_gb_s` across all recorded
1030    /// samples on every device. `0.0`, not `None`, when no sample has
1031    /// ever been recorded -- a genuine "nothing observed yet".
1032    pub peak_bandwidth_achieved: f64,
1033    pub bandwidth_efficiency_by_operation: HashMap<String, f64>,
1034    pub underutilized_devices: Vec<i32>,
1035}
1036
1037#[derive(Debug, Clone, Serialize, Deserialize)]
1038pub struct MemoryPressureSummary {
1039    pub current_pressure_levels: HashMap<i32, MemoryPressureLevel>,
1040    pub pressure_trend: PressureTrend,
1041    pub devices_under_pressure: Vec<i32>,
1042    pub time_in_high_pressure: Duration,
1043}
1044
1045#[derive(Debug, Clone, Serialize, Deserialize)]
1046pub enum PressureTrend {
1047    Decreasing,
1048    Stable,
1049    Increasing,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize)]
1053pub struct AllocationPatternSummary {
1054    pub total_allocations: usize,
1055    pub avg_allocation_size: usize,
1056    pub largest_allocation: usize,
1057    pub allocation_size_distribution: HashMap<String, usize>,
1058    pub memory_leaks_detected: usize,
1059    pub allocation_hot_spots: Vec<AllocationHotSpot>,
1060}
1061
1062#[derive(Debug, Clone, Serialize, Deserialize)]
1063pub struct AllocationHotSpot {
1064    pub location: String,
1065    pub allocation_frequency: f64,
1066    pub total_memory_allocated: usize,
1067    pub avg_allocation_lifetime: Duration,
1068}
1069
1070#[derive(Debug, Clone, Serialize, Deserialize)]
1071pub struct CrossDeviceTransferSummary {
1072    pub total_transfers: usize,
1073    pub total_bytes_transferred: usize,
1074    pub avg_transfer_bandwidth: f64,
1075    /// `None`: computing this needs to know which transfers were really
1076    /// peer-to-peer, which this crate cannot determine -- see
1077    /// [`CrossDeviceTransfer::p2p_enabled`] / `detect_p2p_capability`.
1078    pub p2p_efficiency: Option<f64>,
1079    pub transfer_bottlenecks: Vec<TransferBottleneck>,
1080}
1081
1082#[derive(Debug, Clone, Serialize, Deserialize)]
1083pub struct TransferBottleneck {
1084    pub device_pair: (i32, i32),
1085    pub bottleneck_type: TransferBottleneckType,
1086    pub impact_severity: f64,
1087}
1088
1089#[derive(Debug, Clone, Serialize, Deserialize)]
1090pub enum TransferBottleneckType {
1091    BandwidthLimited,
1092    LatencyBound,
1093    SynchronizationOverhead,
1094    P2PNotAvailable,
1095}
1096
1097#[derive(Debug, Clone, Serialize, Deserialize)]
1098pub struct MemoryOptimizationRecommendation {
1099    pub recommendation_type: MemoryOptimizationType,
1100    pub priority: OptimizationPriority,
1101    pub description: String,
1102    pub expected_benefit: ExpectedBenefit,
1103    pub implementation_steps: Vec<String>,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub enum MemoryOptimizationType {
1108    DefragmentationStrategy,
1109    MemoryPoolingOptimization,
1110    AllocationPatternOptimization,
1111    CrossDeviceTransferOptimization,
1112    PressureReliefStrategy,
1113}
1114
1115#[derive(Debug, Clone, Serialize, Deserialize)]
1116pub enum OptimizationPriority {
1117    Critical,
1118    High,
1119    Medium,
1120    Low,
1121}
1122
1123#[derive(Debug, Clone, Serialize, Deserialize)]
1124pub struct ExpectedBenefit {
1125    pub performance_improvement: f64,
1126    pub memory_efficiency_improvement: f64,
1127    pub implementation_effort: ImplementationDifficulty,
1128}
1129
1130// Default implementations for helper structures
1131
1132impl Default for MemoryAccessPattern {
1133    fn default() -> Self {
1134        Self {
1135            access_frequency: 0.0,
1136            read_ratio: 0.5,
1137            write_ratio: 0.5,
1138            sequential_access_ratio: 0.8,
1139            random_access_ratio: 0.2,
1140            coalesced_access_ratio: 0.9,
1141            cache_hit_rate: 0.85,
1142        }
1143    }
1144}
1145
1146// Constructors and helpers for the remaining structures.
1147
1148/// This crate has no pure-Rust API to query a real GPU's memory capacity
1149/// (a real query needs vendor FFI -- CUDA/ROCm/Metal -- kept out of the
1150/// default build by the COOLJAPAN pure-Rust policy). [`GpuMemoryPool::new`]
1151/// therefore uses this ASSUMED capacity as a documented fallback rather
1152/// than silently pretending to have queried real hardware. It is real
1153/// bookkeeping arithmetic from here on (`allocate`/`deallocate` track
1154/// genuine caller-supplied sizes against it), just seeded from an
1155/// assumption instead of a measurement.
1156const ASSUMED_DEVICE_MEMORY_BYTES: usize = 8 * 1024 * 1024 * 1024; // 8GB
1157
1158impl GpuMemoryPool {
1159    fn new(device_id: i32) -> Result<Self> {
1160        Ok(Self {
1161            device_id,
1162            total_memory: ASSUMED_DEVICE_MEMORY_BYTES,
1163            free_memory: ASSUMED_DEVICE_MEMORY_BYTES,
1164        })
1165    }
1166
1167    fn allocate(&mut self, size: usize) -> Result<()> {
1168        if self.free_memory >= size {
1169            self.free_memory -= size;
1170            Ok(())
1171        } else {
1172            Err(anyhow::anyhow!("Insufficient memory"))
1173        }
1174    }
1175
1176    fn deallocate(&mut self, size: usize) -> Result<()> {
1177        self.free_memory += size;
1178        Ok(())
1179    }
1180
1181    /// `total_memory`/`free_memory` are real (see their own doc comments
1182    /// on [`MemoryFragmentationSnapshot`]); the block-placement fields are
1183    /// honestly `None` -- this pool tracks a free-byte COUNT only, never
1184    /// individual allocation placement, so it cannot know the true shape
1185    /// of its free space.
1186    fn get_fragmentation_snapshot(&self) -> Result<MemoryFragmentationSnapshot> {
1187        Ok(MemoryFragmentationSnapshot {
1188            timestamp: Utc::now(),
1189            device_id: self.device_id,
1190            total_memory: self.total_memory,
1191            free_memory: self.free_memory,
1192            largest_free_block: None,
1193            fragmentation_ratio: None,
1194            free_block_distribution: None,
1195            external_fragmentation: None,
1196            internal_fragmentation: None,
1197        })
1198    }
1199
1200    fn calculate_pressure_level(&self) -> MemoryPressureLevel {
1201        let usage_ratio = 1.0 - (self.free_memory as f64 / self.total_memory as f64);
1202
1203        if usage_ratio > 0.95 {
1204            MemoryPressureLevel::Critical
1205        } else if usage_ratio > 0.85 {
1206            MemoryPressureLevel::High
1207        } else if usage_ratio > 0.70 {
1208            MemoryPressureLevel::Medium
1209        } else {
1210            MemoryPressureLevel::Low
1211        }
1212    }
1213
1214    fn get_available_memory_ratio(&self) -> f64 {
1215        self.free_memory as f64 / self.total_memory as f64
1216    }
1217}
1218
1219impl GpuBandwidthMonitor {
1220    fn new(device_id: i32) -> Result<Self> {
1221        Ok(Self {
1222            device_id,
1223            bandwidth_samples: VecDeque::with_capacity(1000),
1224            theoretical_bandwidth: 900.0, // GB/s for high-end GPU
1225            peak_observed_bandwidth: 0.0,
1226            sustained_bandwidth_history: Vec::new(),
1227        })
1228    }
1229
1230    fn add_sample(&mut self, sample: BandwidthSample) -> Result<()> {
1231        if sample.achieved_bandwidth_gb_s > self.peak_observed_bandwidth {
1232            self.peak_observed_bandwidth = sample.achieved_bandwidth_gb_s;
1233        }
1234
1235        self.bandwidth_samples.push_back(sample);
1236        if self.bandwidth_samples.len() > 1000 {
1237            self.bandwidth_samples.pop_front();
1238        }
1239
1240        Ok(())
1241    }
1242}
1243
1244impl MemoryPressureMonitor {
1245    fn new() -> Self {
1246        Self {
1247            pressure_history: VecDeque::with_capacity(1000),
1248            pressure_thresholds: MemoryPressureThresholds {
1249                medium_threshold: 0.7,
1250                high_threshold: 0.85,
1251                critical_threshold: 0.95,
1252            },
1253            auto_optimization_enabled: true,
1254        }
1255    }
1256
1257    fn add_snapshot(&mut self, snapshot: MemoryPressureSnapshot) {
1258        self.pressure_history.push_back(snapshot);
1259        if self.pressure_history.len() > 1000 {
1260            self.pressure_history.pop_front();
1261        }
1262    }
1263
1264    /// Real per-device most-recent level, real pressure trend, real
1265    /// devices-under-pressure list and a real (attributed-by-gap)
1266    /// time-in-high-pressure -- all from `self.pressure_history`, which
1267    /// this crate already collects on every real `track_allocation`/
1268    /// `track_deallocation` call. Previously discarded its own real
1269    /// history entirely (`_history` was never read).
1270    fn get_summary(&self) -> MemoryPressureSummary {
1271        // `pressure_history` is a `VecDeque` filled via `push_back`, so
1272        // iterating oldest->newest and letting each insert overwrite the
1273        // last correctly leaves the MOST RECENT snapshot per device.
1274        let mut current_pressure_levels: HashMap<i32, MemoryPressureLevel> = HashMap::new();
1275        for snapshot in &self.pressure_history {
1276            current_pressure_levels.insert(snapshot.device_id, snapshot.pressure_level.clone());
1277        }
1278
1279        let devices_under_pressure: Vec<i32> = current_pressure_levels
1280            .iter()
1281            .filter(|(_, level)| {
1282                matches!(
1283                    level,
1284                    MemoryPressureLevel::High | MemoryPressureLevel::Critical
1285                )
1286            })
1287            .map(|(&device_id, _)| device_id)
1288            .collect();
1289
1290        // Real trend: mean pressure ordinal of the recent half of history
1291        // vs. the older half -- the same "insufficient data -> Stable"
1292        // convention used by `GradientAnomalyDetector::analyze_recent_trend`
1293        // elsewhere in this crate.
1294        let pressure_trend = if self.pressure_history.len() < 4 {
1295            PressureTrend::Stable
1296        } else {
1297            let ordinals: Vec<f64> = self
1298                .pressure_history
1299                .iter()
1300                .map(|s| pressure_ordinal(&s.pressure_level))
1301                .collect();
1302            let mid = ordinals.len() / 2;
1303            let older_avg = ordinals[..mid].iter().sum::<f64>() / mid as f64;
1304            let recent_avg = ordinals[mid..].iter().sum::<f64>() / (ordinals.len() - mid) as f64;
1305            let trend_threshold = 0.25;
1306            if recent_avg > older_avg + trend_threshold {
1307                PressureTrend::Increasing
1308            } else if recent_avg < older_avg - trend_threshold {
1309                PressureTrend::Decreasing
1310            } else {
1311                PressureTrend::Stable
1312            }
1313        };
1314
1315        // Real time spent at High/Critical: attribute each real
1316        // inter-snapshot gap (real timestamps) to the level held at the
1317        // START of that gap, per device, and sum the gaps that started
1318        // High or Critical.
1319        let mut by_device: HashMap<i32, Vec<&MemoryPressureSnapshot>> = HashMap::new();
1320        for snapshot in &self.pressure_history {
1321            by_device.entry(snapshot.device_id).or_default().push(snapshot);
1322        }
1323        let mut time_in_high_pressure = Duration::from_secs(0);
1324        for snapshots in by_device.values_mut() {
1325            snapshots.sort_by_key(|s| s.timestamp);
1326            for pair in snapshots.windows(2) {
1327                let (a, b) = (pair[0], pair[1]);
1328                if matches!(
1329                    a.pressure_level,
1330                    MemoryPressureLevel::High | MemoryPressureLevel::Critical
1331                ) {
1332                    if let Ok(gap) = (b.timestamp - a.timestamp).to_std() {
1333                        time_in_high_pressure += gap;
1334                    }
1335                }
1336            }
1337        }
1338
1339        MemoryPressureSummary {
1340            current_pressure_levels,
1341            pressure_trend,
1342            devices_under_pressure,
1343            time_in_high_pressure,
1344        }
1345    }
1346}
1347
1348/// Ordinal encoding of [`MemoryPressureLevel`] for trend averaging (higher
1349/// = more pressure). An internal convenience, not a claim of any inherent
1350/// numeric scale in the real telemetry.
1351fn pressure_ordinal(level: &MemoryPressureLevel) -> f64 {
1352    match level {
1353        MemoryPressureLevel::Low => 0.0,
1354        MemoryPressureLevel::Medium => 1.0,
1355        MemoryPressureLevel::High => 2.0,
1356        MemoryPressureLevel::Critical => 3.0,
1357    }
1358}
1359
1360// Real summary aggregation from the real data each analyzer already
1361// collects (previously discarded via an unused `_history`/`_monitors`/
1362// `_allocations`/`_transfers` parameter in every one of the four `new`s
1363// below).
1364
1365impl FragmentationSummary {
1366    fn new(history: &VecDeque<MemoryFragmentationSnapshot>) -> Self {
1367        let measured: Vec<(i32, f64)> = history
1368            .iter()
1369            .filter_map(|s| s.fragmentation_ratio.map(|r| (s.device_id, r)))
1370            .collect();
1371
1372        let Some(&(first_device, _)) = measured.first() else {
1373            // No snapshot in this history has ever carried a real
1374            // fragmentation ratio -- see that field's own doc comment.
1375            // Honestly absent, never the old `0.1`/`0.2` constants.
1376            return Self {
1377                avg_fragmentation_ratio: None,
1378                peak_fragmentation_ratio: None,
1379                fragmentation_trend: FragmentationTrend::Stable,
1380                most_fragmented_device: None,
1381            };
1382        };
1383
1384        let avg = measured.iter().map(|(_, r)| r).sum::<f64>() / measured.len() as f64;
1385        let (peak_device, peak_ratio) =
1386            measured.iter().fold((first_device, f64::MIN), |(bd, br), &(d, r)| {
1387                if r > br {
1388                    (d, r)
1389                } else {
1390                    (bd, br)
1391                }
1392            });
1393
1394        let fragmentation_trend = if measured.len() < 4 {
1395            FragmentationTrend::Stable
1396        } else {
1397            let mid = measured.len() / 2;
1398            let older_avg = measured[..mid].iter().map(|(_, r)| r).sum::<f64>() / mid as f64;
1399            let recent_avg =
1400                measured[mid..].iter().map(|(_, r)| r).sum::<f64>() / (measured.len() - mid) as f64;
1401            let trend_threshold = 0.05;
1402            if recent_avg > older_avg + trend_threshold {
1403                FragmentationTrend::Worsening
1404            } else if recent_avg < older_avg - trend_threshold {
1405                FragmentationTrend::Improving
1406            } else {
1407                FragmentationTrend::Stable
1408            }
1409        };
1410
1411        Self {
1412            avg_fragmentation_ratio: Some(avg),
1413            peak_fragmentation_ratio: Some(peak_ratio),
1414            fragmentation_trend,
1415            most_fragmented_device: Some(peak_device),
1416        }
1417    }
1418}
1419
1420impl BandwidthSummary {
1421    fn new(monitors: &HashMap<i32, GpuBandwidthMonitor>) -> Self {
1422        let peak_bandwidth_achieved =
1423            monitors.values().map(|m| m.peak_observed_bandwidth).fold(0.0_f64, f64::max);
1424
1425        // Real average `efficiency_percentage` per real operation type --
1426        // each sample's value comes from whatever called
1427        // `record_bandwidth_sample`, not fabricated by this crate.
1428        let mut efficiency_sum: HashMap<String, (f64, usize)> = HashMap::new();
1429        for monitor in monitors.values() {
1430            for sample in &monitor.bandwidth_samples {
1431                let entry = efficiency_sum
1432                    .entry(format!("{:?}", sample.operation_type))
1433                    .or_insert((0.0, 0));
1434                entry.0 += sample.efficiency_percentage;
1435                entry.1 += 1;
1436            }
1437        }
1438        let bandwidth_efficiency_by_operation: HashMap<String, f64> = efficiency_sum
1439            .into_iter()
1440            .map(|(op, (sum, count))| (op, sum / count as f64))
1441            .collect();
1442
1443        // Real, RELATIVE under-utilization: a device whose peak observed
1444        // bandwidth sits well below the best peak observed anywhere,
1445        // among devices with at least one real sample -- never compared
1446        // against `theoretical_bandwidth` (a documented assumption, not a
1447        // measurement). Zero samples is "unproven", not "underutilized".
1448        let underutilized_devices: Vec<i32> = if peak_bandwidth_achieved > 0.0 {
1449            monitors
1450                .iter()
1451                .filter(|(_, m)| !m.bandwidth_samples.is_empty())
1452                .filter(|(_, m)| m.peak_observed_bandwidth < peak_bandwidth_achieved * 0.5)
1453                .map(|(&device_id, _)| device_id)
1454                .collect()
1455        } else {
1456            Vec::new()
1457        };
1458
1459        Self {
1460            avg_bandwidth_utilization: None,
1461            peak_bandwidth_achieved,
1462            bandwidth_efficiency_by_operation,
1463            underutilized_devices,
1464        }
1465    }
1466}
1467
1468impl AllocationPatternSummary {
1469    fn new(allocations: &HashMap<Uuid, GpuMemoryAllocation>) -> Self {
1470        let total_allocations = allocations.len();
1471        let total_bytes: u128 = allocations.values().map(|a| a.size_bytes as u128).sum();
1472        let avg_allocation_size = if total_allocations > 0 {
1473            (total_bytes / total_allocations as u128) as usize
1474        } else {
1475            0
1476        };
1477        let largest_allocation = allocations.values().map(|a| a.size_bytes).max().unwrap_or(0);
1478
1479        let mut allocation_size_distribution: HashMap<String, usize> = HashMap::new();
1480        for allocation in allocations.values() {
1481            *allocation_size_distribution
1482                .entry(format!("{:?}", allocation.memory_type))
1483                .or_insert(0) += 1;
1484        }
1485
1486        // Heuristic, documented as such (not a certainty): an allocation
1487        // still outstanding after this long is flagged as a possible
1488        // leak. Real elapsed time from the real allocation timestamp;
1489        // never a fabricated count -- the old code reported `0` always,
1490        // even with real un-freed allocations on record.
1491        const LEAK_SUSPECT_THRESHOLD: Duration = Duration::from_secs(300);
1492        let memory_leaks_detected = allocations
1493            .values()
1494            .filter(|a| {
1495                !a.freed && a.timestamp.elapsed().unwrap_or_default() > LEAK_SUSPECT_THRESHOLD
1496            })
1497            .count();
1498
1499        // Real hot spots: group by the most specific real context label
1500        // available, summing real bytes. `allocation_frequency` is a real
1501        // rate (count / the real observed timestamp span across ALL
1502        // tracked allocations), falling back to the raw real count only
1503        // when that span is degenerate (e.g. a single allocation).
1504        // `avg_allocation_lifetime` averages real `free_timestamp -
1505        // timestamp` deltas over allocations that HAVE been freed at that
1506        // location -- one still outstanding has no real lifetime yet.
1507        let observation_span_secs = {
1508            let timestamps: Vec<SystemTime> = allocations.values().map(|a| a.timestamp).collect();
1509            match (timestamps.iter().min(), timestamps.iter().max()) {
1510                (Some(&min_t), Some(&max_t)) => {
1511                    max_t.duration_since(min_t).unwrap_or_default().as_secs_f64()
1512                },
1513                _ => 0.0,
1514            }
1515        };
1516
1517        let mut by_location: HashMap<String, (usize, u64, Duration, usize)> = HashMap::new();
1518        for allocation in allocations.values() {
1519            let location = allocation
1520                .allocation_context
1521                .tensor_name
1522                .clone()
1523                .or_else(|| allocation.allocation_context.layer_name.clone())
1524                .or_else(|| allocation.allocation_context.kernel_name.clone())
1525                .unwrap_or_else(|| {
1526                    format!("{:?}", allocation.allocation_context.allocation_source)
1527                });
1528            let entry = by_location.entry(location).or_insert((0, 0, Duration::ZERO, 0));
1529            entry.0 += 1;
1530            entry.1 += allocation.size_bytes as u64;
1531            if let Some(free_time) = allocation.free_timestamp {
1532                if let Ok(lifetime) = free_time.duration_since(allocation.timestamp) {
1533                    entry.2 += lifetime;
1534                    entry.3 += 1;
1535                }
1536            }
1537        }
1538        let mut allocation_hot_spots: Vec<AllocationHotSpot> = by_location
1539            .into_iter()
1540            .map(
1541                |(location, (count, bytes, total_lifetime, freed_count))| AllocationHotSpot {
1542                    location,
1543                    allocation_frequency: if observation_span_secs > 0.0 {
1544                        count as f64 / observation_span_secs
1545                    } else {
1546                        count as f64
1547                    },
1548                    total_memory_allocated: bytes as usize,
1549                    avg_allocation_lifetime: if freed_count > 0 {
1550                        total_lifetime / freed_count as u32
1551                    } else {
1552                        Duration::ZERO
1553                    },
1554                },
1555            )
1556            .collect();
1557        allocation_hot_spots.sort_by_key(|h| std::cmp::Reverse(h.total_memory_allocated));
1558        allocation_hot_spots.truncate(10);
1559
1560        Self {
1561            total_allocations,
1562            avg_allocation_size,
1563            largest_allocation,
1564            allocation_size_distribution,
1565            memory_leaks_detected,
1566            allocation_hot_spots,
1567        }
1568    }
1569}
1570
1571impl CrossDeviceTransferSummary {
1572    fn new(transfers: &[CrossDeviceTransfer]) -> Self {
1573        let total_transfers = transfers.len();
1574        let total_bytes_transferred: usize = transfers.iter().map(|t| t.bytes_transferred).sum();
1575        let avg_transfer_bandwidth = if total_transfers > 0 {
1576            transfers.iter().map(|t| t.bandwidth_achieved).sum::<f64>() / total_transfers as f64
1577        } else {
1578            0.0
1579        };
1580
1581        // Real, RELATIVE bottleneck detection: a (source, target) device
1582        // pair whose average achieved bandwidth sits well below the
1583        // overall average across every pair -- a genuine comparison
1584        // against other real measurements, never an absolute claim this
1585        // crate cannot verify (e.g. "P2P not available", which needs a
1586        // real capability query -- see `detect_p2p_capability`).
1587        let mut by_pair: HashMap<(i32, i32), Vec<f64>> = HashMap::new();
1588        for t in transfers {
1589            by_pair
1590                .entry((t.source_device, t.target_device))
1591                .or_default()
1592                .push(t.bandwidth_achieved);
1593        }
1594        let transfer_bottlenecks: Vec<TransferBottleneck> = if avg_transfer_bandwidth > 0.0 {
1595            by_pair
1596                .into_iter()
1597                .filter_map(|(pair, bandwidths)| {
1598                    let pair_avg = bandwidths.iter().sum::<f64>() / bandwidths.len() as f64;
1599                    if pair_avg < avg_transfer_bandwidth * 0.5 {
1600                        Some(TransferBottleneck {
1601                            device_pair: pair,
1602                            bottleneck_type: TransferBottleneckType::BandwidthLimited,
1603                            impact_severity: (1.0 - pair_avg / avg_transfer_bandwidth)
1604                                .clamp(0.0, 1.0),
1605                        })
1606                    } else {
1607                        None
1608                    }
1609                })
1610                .collect()
1611        } else {
1612            Vec::new()
1613        };
1614
1615        Self {
1616            total_transfers,
1617            total_bytes_transferred,
1618            avg_transfer_bandwidth,
1619            // Needs to know which transfers were really P2P, which this
1620            // crate cannot determine -- see `CrossDeviceTransfer::p2p_enabled`.
1621            p2p_efficiency: None,
1622            transfer_bottlenecks,
1623        }
1624    }
1625}
1626
1627#[derive(Debug)]
1628struct GpuMemoryPool {
1629    device_id: i32,
1630    total_memory: usize,
1631    free_memory: usize,
1632}
1633
1634/// Configuration for advanced GPU profiling
1635#[derive(Debug, Clone, Serialize, Deserialize)]
1636pub struct AdvancedGpuProfilingConfig {
1637    /// Enable GPU profiling
1638    pub enable_gpu_profiling: bool,
1639    /// Number of GPU devices to profile
1640    pub device_count: i32,
1641    /// Enable memory profiling
1642    pub enable_memory_profiling: bool,
1643    /// Enable kernel profiling
1644    pub enable_kernel_profiling: bool,
1645    /// Enable bandwidth monitoring
1646    pub enable_bandwidth_monitoring: bool,
1647    /// Maximum number of allocations to track
1648    pub max_tracked_allocations: usize,
1649    /// Sampling rate for profiling (0.0 to 1.0)
1650    pub profiling_sampling_rate: f32,
1651    /// Enable fragmentation analysis
1652    pub enable_fragmentation_analysis: bool,
1653}
1654
1655impl Default for AdvancedGpuProfilingConfig {
1656    fn default() -> Self {
1657        Self {
1658            enable_gpu_profiling: true,
1659            device_count: 1,
1660            enable_memory_profiling: true,
1661            enable_kernel_profiling: true,
1662            enable_bandwidth_monitoring: true,
1663            max_tracked_allocations: 10000,
1664            profiling_sampling_rate: 1.0,
1665            enable_fragmentation_analysis: true,
1666        }
1667    }
1668}
1669
1670/// Summary report for kernel optimization
1671#[derive(Debug, Clone, Serialize, Deserialize)]
1672pub struct KernelOptimizationSummaryReport {
1673    pub total_kernels_analyzed: usize,
1674    pub optimization_opportunities_found: usize,
1675    pub high_impact_optimizations: Vec<HighImpactOptimization>,
1676    pub fusion_opportunities: usize,
1677    pub regression_alerts: usize,
1678    /// Composite score in `[0, 100]`, or `None` when no kernel has been
1679    /// analysed yet and there is therefore nothing to score.
1680    pub overall_optimization_score: Option<f64>,
1681    pub top_recommendations: Vec<String>,
1682}
1683
1684#[derive(Debug, Clone, Serialize, Deserialize)]
1685pub struct HighImpactOptimization {
1686    pub kernel_name: String,
1687    pub optimization_type: String,
1688    pub expected_speedup: f64,
1689    pub implementation_difficulty: String,
1690    pub description: String,
1691}
1692
1693#[cfg(test)]
1694#[path = "advanced_gpu_profiler_tests.rs"]
1695mod advanced_gpu_profiler_tests;