Skip to main content

amari_gpu/
timeline.rs

1//! GPU Timeline Analysis and Performance Profiling Infrastructure
2//!
3//! This module provides advanced GPU profiling capabilities with timeline analysis,
4//! bottleneck detection, and multi-GPU performance optimization insights.
5
6use crate::{DeviceId, GpuDevice, UnifiedGpuResult};
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10
11/// GPU timeline event representing a specific operation
12#[derive(Debug, Clone)]
13pub struct TimelineEvent {
14    pub event_id: String,
15    pub device_id: DeviceId,
16    pub operation_type: String,
17    pub start_time: Instant,
18    pub end_time: Option<Instant>,
19    pub gpu_timestamp_start: Option<u64>,
20    pub gpu_timestamp_end: Option<u64>,
21    pub memory_usage_mb: f32,
22    pub workgroup_config: (u32, u32, u32),
23    pub buffer_sizes: Vec<u64>,
24    pub metadata: HashMap<String, String>,
25}
26
27impl TimelineEvent {
28    /// Create a new timeline event
29    pub fn new(
30        event_id: String,
31        device_id: DeviceId,
32        operation_type: String,
33        memory_usage_mb: f32,
34        workgroup_config: (u32, u32, u32),
35        buffer_sizes: Vec<u64>,
36    ) -> Self {
37        Self {
38            event_id,
39            device_id,
40            operation_type,
41            start_time: Instant::now(),
42            end_time: None,
43            gpu_timestamp_start: None,
44            gpu_timestamp_end: None,
45            memory_usage_mb,
46            workgroup_config,
47            buffer_sizes,
48            metadata: HashMap::new(),
49        }
50    }
51
52    /// Mark the event as completed
53    pub fn complete(&mut self) {
54        self.end_time = Some(Instant::now());
55    }
56
57    /// Get the CPU duration of the event
58    pub fn cpu_duration(&self) -> Option<Duration> {
59        self.end_time.map(|end| end.duration_since(self.start_time))
60    }
61
62    /// Get the GPU duration of the event (if available)
63    pub fn gpu_duration_ns(&self) -> Option<u64> {
64        match (self.gpu_timestamp_start, self.gpu_timestamp_end) {
65            (Some(start), Some(end)) => end.checked_sub(start),
66            _ => None,
67        }
68    }
69
70    /// Calculate memory bandwidth utilization
71    pub fn memory_bandwidth_gb_s(&self) -> f32 {
72        if let Some(duration) = self.cpu_duration() {
73            let total_bytes: u64 = self.buffer_sizes.iter().sum::<u64>() * 2; // Read + Write
74            let duration_s = duration.as_secs_f32();
75            if duration_s > 0.0 {
76                (total_bytes as f32) / duration_s / 1e9
77            } else {
78                0.0
79            }
80        } else {
81            0.0
82        }
83    }
84
85    /// Add metadata to the event
86    pub fn add_metadata(&mut self, key: String, value: String) {
87        self.metadata.insert(key, value);
88    }
89}
90
91/// Timeline analyzer for GPU performance analysis
92pub struct GpuTimelineAnalyzer {
93    events: Arc<Mutex<VecDeque<TimelineEvent>>>,
94    max_events: usize,
95    devices: Arc<Mutex<HashMap<DeviceId, Arc<GpuDevice>>>>,
96}
97
98impl GpuTimelineAnalyzer {
99    /// Create a new timeline analyzer
100    pub fn new(max_events: usize) -> Self {
101        Self {
102            events: Arc::new(Mutex::new(VecDeque::with_capacity(max_events))),
103            max_events,
104            devices: Arc::new(Mutex::new(HashMap::new())),
105        }
106    }
107
108    /// Add a device to track
109    pub fn add_device(&self, device: Arc<GpuDevice>) {
110        if let Ok(mut devices) = self.devices.lock() {
111            devices.insert(device.id, device);
112        }
113    }
114
115    /// Record a timeline event
116    pub fn record_event(&self, event: TimelineEvent) {
117        if let Ok(mut events) = self.events.lock() {
118            events.push_back(event);
119
120            // Keep only the most recent events
121            while events.len() > self.max_events {
122                events.pop_front();
123            }
124        }
125    }
126
127    /// Get all events in the specified time range
128    pub fn get_events_in_range(&self, start: Instant, end: Instant) -> Vec<TimelineEvent> {
129        if let Ok(events) = self.events.lock() {
130            events
131                .iter()
132                .filter(|event| event.start_time >= start && event.start_time <= end)
133                .cloned()
134                .collect()
135        } else {
136            Vec::new()
137        }
138    }
139
140    /// Get events for a specific device
141    pub fn get_device_events(
142        &self,
143        device_id: DeviceId,
144        limit: Option<usize>,
145    ) -> Vec<TimelineEvent> {
146        if let Ok(events) = self.events.lock() {
147            let mut device_events: Vec<_> = events
148                .iter()
149                .filter(|event| event.device_id == device_id)
150                .cloned()
151                .collect();
152
153            if let Some(limit) = limit {
154                device_events.truncate(limit);
155            }
156
157            device_events
158        } else {
159            Vec::new()
160        }
161    }
162
163    /// Analyze GPU utilization over time
164    pub fn analyze_gpu_utilization(&self, window_duration: Duration) -> UtilizationAnalysis {
165        let now = Instant::now();
166        if window_duration.is_zero() {
167            return UtilizationAnalysis {
168                analysis_window: window_duration,
169                device_stats: HashMap::new(),
170                timestamp: now,
171            };
172        }
173        let window_start = now - window_duration;
174
175        let events = self.get_events_in_range(window_start, now);
176        let mut device_utilization = HashMap::new();
177
178        // Group events by device
179        for event in events {
180            let device_events = device_utilization
181                .entry(event.device_id)
182                .or_insert_with(Vec::new);
183            device_events.push(event);
184        }
185
186        let mut device_stats = HashMap::new();
187
188        for (device_id, events) in device_utilization {
189            let total_duration: Duration =
190                events.iter().filter_map(|event| event.cpu_duration()).sum();
191
192            let utilization_percent =
193                (total_duration.as_secs_f32() / window_duration.as_secs_f32()) * 100.0;
194            let utilization_percent = utilization_percent.min(100.0); // Cap at 100%
195
196            let avg_memory_bandwidth = if !events.is_empty() {
197                events
198                    .iter()
199                    .map(|e| e.memory_bandwidth_gb_s())
200                    .sum::<f32>()
201                    / events.len() as f32
202            } else {
203                0.0
204            };
205
206            device_stats.insert(
207                device_id,
208                DeviceUtilizationStats {
209                    utilization_percent,
210                    operation_count: events.len(),
211                    avg_memory_bandwidth_gb_s: avg_memory_bandwidth,
212                    total_duration,
213                },
214            );
215        }
216
217        UtilizationAnalysis {
218            analysis_window: window_duration,
219            device_stats,
220            timestamp: now,
221        }
222    }
223
224    /// Detect performance bottlenecks
225    pub fn detect_bottlenecks(&self, analysis_window: Duration) -> BottleneckAnalysis {
226        let utilization = self.analyze_gpu_utilization(analysis_window);
227        let events = self.get_events_in_range(Instant::now() - analysis_window, Instant::now());
228
229        let mut bottlenecks = Vec::new();
230
231        // Analyze GPU utilization bottlenecks
232        for (device_id, stats) in &utilization.device_stats {
233            if stats.utilization_percent < 50.0 {
234                bottlenecks.push(PerformanceBottleneck::LowGpuUtilization {
235                    device_id: *device_id,
236                    utilization_percent: stats.utilization_percent,
237                    recommendation: "Consider increasing batch size or workload complexity"
238                        .to_string(),
239                });
240            }
241
242            if stats.avg_memory_bandwidth_gb_s < 100.0 {
243                // Assuming 100 GB/s baseline
244                bottlenecks.push(PerformanceBottleneck::MemoryBandwidthUnderutilized {
245                    device_id: *device_id,
246                    bandwidth_gb_s: stats.avg_memory_bandwidth_gb_s,
247                    recommendation: "Optimize memory access patterns or increase data parallelism"
248                        .to_string(),
249                });
250            }
251        }
252
253        // Analyze synchronization bottlenecks
254        let sync_analysis = self.analyze_synchronization_overhead(&events);
255        if sync_analysis.avg_sync_overhead_percent > 20.0 {
256            bottlenecks.push(PerformanceBottleneck::SynchronizationOverhead {
257                overhead_percent: sync_analysis.avg_sync_overhead_percent,
258                recommendation: "Reduce synchronization frequency or use asynchronous operations"
259                    .to_string(),
260            });
261        }
262
263        // Analyze workgroup efficiency
264        let workgroup_analysis = self.analyze_workgroup_efficiency(&events);
265        for (device_id, efficiency) in workgroup_analysis {
266            if efficiency < 70.0 {
267                bottlenecks.push(PerformanceBottleneck::InefficientWorkgroups {
268                    device_id,
269                    efficiency_percent: efficiency,
270                    recommendation: "Optimize workgroup size or shared memory usage".to_string(),
271                });
272            }
273        }
274
275        let recommendations = self.generate_optimization_recommendations(&bottlenecks);
276
277        BottleneckAnalysis {
278            analysis_window,
279            bottlenecks,
280            recommendations,
281            timestamp: Instant::now(),
282        }
283    }
284
285    /// Analyze synchronization overhead
286    fn analyze_synchronization_overhead(
287        &self,
288        events: &[TimelineEvent],
289    ) -> SynchronizationAnalysis {
290        let mut total_operation_time = Duration::ZERO;
291        let mut total_sync_time = Duration::ZERO;
292
293        // Group events by operation type to identify synchronization patterns
294        let mut operation_groups = HashMap::new();
295        for event in events {
296            let group = operation_groups
297                .entry(&event.operation_type)
298                .or_insert_with(Vec::new);
299            group.push(event);
300        }
301
302        // Estimate synchronization overhead by looking at gaps between operations
303        for (_op_type, group_events) in operation_groups {
304            for window in group_events.windows(2) {
305                if let [event1, event2] = window {
306                    if let (Some(end1), start2) = (event1.end_time, event2.start_time) {
307                        if event1.device_id != event2.device_id {
308                            // Cross-device gap indicates potential synchronization
309                            let gap = start2.duration_since(end1);
310                            total_sync_time += gap;
311                        }
312                        if let Some(duration1) = event1.cpu_duration() {
313                            total_operation_time += duration1;
314                        }
315                    }
316                }
317            }
318        }
319
320        let sync_overhead_percent = if total_operation_time.as_nanos() > 0 {
321            (total_sync_time.as_nanos() as f32 / total_operation_time.as_nanos() as f32) * 100.0
322        } else {
323            0.0
324        };
325
326        SynchronizationAnalysis {
327            total_sync_time,
328            total_operation_time,
329            avg_sync_overhead_percent: sync_overhead_percent,
330            cross_device_operations: events.len(),
331        }
332    }
333
334    /// Analyze workgroup efficiency
335    fn analyze_workgroup_efficiency(&self, events: &[TimelineEvent]) -> HashMap<DeviceId, f32> {
336        let mut device_efficiency = HashMap::new();
337
338        for event in events {
339            if let Some(_duration) = event.cpu_duration() {
340                let (x, y, z) = event.workgroup_config;
341                let total_threads = x * y * z;
342
343                // Estimate efficiency based on workgroup utilization
344                // This is a simplified heuristic - in practice, you'd use GPU profiling data
345                let theoretical_max_threads = 1024; // Common maximum
346                let utilization = (total_threads as f32 / theoretical_max_threads as f32).min(1.0);
347
348                // Factor in memory bandwidth and duration
349                let memory_efficiency = (event.memory_bandwidth_gb_s() / 500.0).min(1.0); // Normalize to 500 GB/s
350
351                let efficiency = (utilization * 0.6 + memory_efficiency * 0.4) * 100.0;
352
353                let current_efficiency = device_efficiency.entry(event.device_id).or_insert(0.0);
354                *current_efficiency = (*current_efficiency + efficiency) / 2.0; // Running average
355            }
356        }
357
358        device_efficiency
359    }
360
361    /// Generate optimization recommendations
362    fn generate_optimization_recommendations(
363        &self,
364        bottlenecks: &[PerformanceBottleneck],
365    ) -> Vec<OptimizationRecommendation> {
366        let mut recommendations = Vec::new();
367
368        // Count bottleneck types
369        let mut low_utilization_count = 0;
370        let mut memory_issues = 0;
371        let mut sync_issues = 0;
372        let mut workgroup_issues = 0;
373
374        for bottleneck in bottlenecks {
375            match bottleneck {
376                PerformanceBottleneck::LowGpuUtilization { .. } => low_utilization_count += 1,
377                PerformanceBottleneck::MemoryBandwidthUnderutilized { .. } => memory_issues += 1,
378                PerformanceBottleneck::SynchronizationOverhead { .. } => sync_issues += 1,
379                PerformanceBottleneck::InefficientWorkgroups { .. } => workgroup_issues += 1,
380            }
381        }
382
383        // Generate high-level recommendations
384        if low_utilization_count > 0 {
385            recommendations.push(OptimizationRecommendation {
386                priority: RecommendationPriority::High,
387                category: "GPU Utilization".to_string(),
388                description: "Multiple devices showing low utilization".to_string(),
389                action: "Consider increasing batch sizes or enabling more parallel operations"
390                    .to_string(),
391                estimated_improvement: format!("{}% performance gain", low_utilization_count * 15),
392            });
393        }
394
395        if memory_issues > 0 {
396            recommendations.push(OptimizationRecommendation {
397                priority: RecommendationPriority::Medium,
398                category: "Memory Optimization".to_string(),
399                description: "Memory bandwidth underutilized".to_string(),
400                action: "Optimize data layouts and reduce memory transfer overhead".to_string(),
401                estimated_improvement: "10-25% performance gain".to_string(),
402            });
403        }
404
405        if sync_issues > 0 {
406            recommendations.push(OptimizationRecommendation {
407                priority: RecommendationPriority::High,
408                category: "Synchronization".to_string(),
409                description: "High synchronization overhead detected".to_string(),
410                action: "Implement asynchronous operations and reduce cross-device dependencies"
411                    .to_string(),
412                estimated_improvement: "20-40% performance gain".to_string(),
413            });
414        }
415
416        if workgroup_issues > 0 {
417            recommendations.push(OptimizationRecommendation {
418                priority: RecommendationPriority::Low,
419                category: "Workgroup Configuration".to_string(),
420                description: "Suboptimal workgroup configurations".to_string(),
421                action: "Tune workgroup sizes and shared memory usage".to_string(),
422                estimated_improvement: "5-15% performance gain".to_string(),
423            });
424        }
425
426        recommendations
427    }
428}
429
430/// Device utilization statistics
431#[derive(Debug, Clone)]
432pub struct DeviceUtilizationStats {
433    pub utilization_percent: f32,
434    pub operation_count: usize,
435    pub avg_memory_bandwidth_gb_s: f32,
436    pub total_duration: Duration,
437}
438
439/// GPU utilization analysis result
440#[derive(Debug, Clone)]
441pub struct UtilizationAnalysis {
442    pub analysis_window: Duration,
443    pub device_stats: HashMap<DeviceId, DeviceUtilizationStats>,
444    pub timestamp: Instant,
445}
446
447/// Synchronization analysis result
448#[derive(Debug, Clone)]
449pub struct SynchronizationAnalysis {
450    pub total_sync_time: Duration,
451    pub total_operation_time: Duration,
452    pub avg_sync_overhead_percent: f32,
453    pub cross_device_operations: usize,
454}
455
456/// Performance bottleneck types
457#[derive(Debug, Clone)]
458pub enum PerformanceBottleneck {
459    LowGpuUtilization {
460        device_id: DeviceId,
461        utilization_percent: f32,
462        recommendation: String,
463    },
464    MemoryBandwidthUnderutilized {
465        device_id: DeviceId,
466        bandwidth_gb_s: f32,
467        recommendation: String,
468    },
469    SynchronizationOverhead {
470        overhead_percent: f32,
471        recommendation: String,
472    },
473    InefficientWorkgroups {
474        device_id: DeviceId,
475        efficiency_percent: f32,
476        recommendation: String,
477    },
478}
479
480/// Bottleneck analysis result
481#[derive(Debug, Clone)]
482pub struct BottleneckAnalysis {
483    pub analysis_window: Duration,
484    pub bottlenecks: Vec<PerformanceBottleneck>,
485    pub recommendations: Vec<OptimizationRecommendation>,
486    pub timestamp: Instant,
487}
488
489/// Optimization recommendation priority
490#[derive(Debug, Clone)]
491pub enum RecommendationPriority {
492    Low,
493    Medium,
494    High,
495    Critical,
496}
497
498/// Optimization recommendation
499#[derive(Debug, Clone)]
500pub struct OptimizationRecommendation {
501    pub priority: RecommendationPriority,
502    pub category: String,
503    pub description: String,
504    pub action: String,
505    pub estimated_improvement: String,
506}
507
508/// Multi-GPU performance monitor
509pub struct MultiGpuPerformanceMonitor {
510    timeline_analyzer: GpuTimelineAnalyzer,
511    monitoring_enabled: bool,
512    analysis_interval: Duration,
513    last_analysis: Instant,
514}
515
516impl MultiGpuPerformanceMonitor {
517    /// Create a new performance monitor
518    pub fn new(max_events: usize, analysis_interval: Duration) -> Self {
519        Self {
520            timeline_analyzer: GpuTimelineAnalyzer::new(max_events),
521            monitoring_enabled: true,
522            analysis_interval,
523            last_analysis: Instant::now(),
524        }
525    }
526
527    /// Add a device to monitor
528    pub fn add_device(&self, device: Arc<GpuDevice>) {
529        self.timeline_analyzer.add_device(device);
530    }
531
532    /// Start monitoring an operation
533    pub fn start_operation(
534        &self,
535        operation_id: String,
536        device_id: DeviceId,
537        operation_type: String,
538        memory_usage_mb: f32,
539        workgroup_config: (u32, u32, u32),
540        buffer_sizes: Vec<u64>,
541    ) -> OperationHandle<'_> {
542        let event = TimelineEvent::new(
543            operation_id.clone(),
544            device_id,
545            operation_type,
546            memory_usage_mb,
547            workgroup_config,
548            buffer_sizes,
549        );
550
551        OperationHandle {
552            event,
553            monitor: self,
554        }
555    }
556
557    /// Complete an operation
558    fn complete_operation(&self, mut event: TimelineEvent) {
559        if self.monitoring_enabled {
560            event.complete();
561            self.timeline_analyzer.record_event(event);
562        }
563    }
564
565    /// Get performance analysis
566    pub fn get_performance_analysis(
567        &self,
568        window_duration: Duration,
569    ) -> UnifiedGpuResult<PerformanceAnalysisReport> {
570        let utilization = self
571            .timeline_analyzer
572            .analyze_gpu_utilization(window_duration);
573        let bottlenecks = self.timeline_analyzer.detect_bottlenecks(window_duration);
574
575        Ok(PerformanceAnalysisReport {
576            utilization_analysis: utilization,
577            bottleneck_analysis: bottlenecks,
578            timestamp: Instant::now(),
579        })
580    }
581
582    /// Enable or disable monitoring
583    pub fn set_monitoring_enabled(&mut self, enabled: bool) {
584        self.monitoring_enabled = enabled;
585    }
586
587    /// Check if automatic analysis should be performed
588    pub fn should_perform_analysis(&self) -> bool {
589        self.monitoring_enabled && self.last_analysis.elapsed() >= self.analysis_interval
590    }
591}
592
593/// Handle for tracking an operation
594pub struct OperationHandle<'a> {
595    event: TimelineEvent,
596    monitor: &'a MultiGpuPerformanceMonitor,
597}
598
599impl<'a> OperationHandle<'a> {
600    /// Add metadata to the operation
601    pub fn add_metadata(&mut self, key: String, value: String) {
602        self.event.add_metadata(key, value);
603    }
604
605    /// Set GPU timestamps
606    pub fn set_gpu_timestamps(&mut self, start: u64, end: u64) {
607        self.event.gpu_timestamp_start = Some(start);
608        self.event.gpu_timestamp_end = Some(end);
609    }
610}
611
612impl<'a> Drop for OperationHandle<'a> {
613    fn drop(&mut self) {
614        // Automatically complete the operation when the handle is dropped
615        let event = std::mem::replace(
616            &mut self.event,
617            TimelineEvent::new(
618                "dropped".to_string(),
619                crate::DeviceId(0),
620                "dropped".to_string(),
621                0.0,
622                (1, 1, 1),
623                vec![],
624            ),
625        );
626        self.monitor.complete_operation(event);
627    }
628}
629
630/// Combined performance analysis report
631#[derive(Debug, Clone)]
632pub struct PerformanceAnalysisReport {
633    pub utilization_analysis: UtilizationAnalysis,
634    pub bottleneck_analysis: BottleneckAnalysis,
635    pub timestamp: Instant,
636}
637
638impl PerformanceAnalysisReport {
639    /// Get overall performance score (0-100)
640    pub fn overall_performance_score(&self) -> f32 {
641        let avg_utilization = if !self.utilization_analysis.device_stats.is_empty() {
642            self.utilization_analysis
643                .device_stats
644                .values()
645                .map(|stats| stats.utilization_percent)
646                .sum::<f32>()
647                / self.utilization_analysis.device_stats.len() as f32
648        } else {
649            0.0
650        };
651
652        // Penalize for bottlenecks
653        let bottleneck_penalty = self.bottleneck_analysis.bottlenecks.len() as f32 * 5.0;
654        let score = avg_utilization - bottleneck_penalty;
655        score.clamp(0.0, 100.0)
656    }
657
658    /// Get summary statistics
659    pub fn get_summary(&self) -> PerformanceSummary {
660        let total_devices = self.utilization_analysis.device_stats.len();
661        let high_priority_issues = self
662            .bottleneck_analysis
663            .recommendations
664            .iter()
665            .filter(|rec| {
666                matches!(
667                    rec.priority,
668                    RecommendationPriority::High | RecommendationPriority::Critical
669                )
670            })
671            .count();
672
673        PerformanceSummary {
674            overall_score: self.overall_performance_score(),
675            total_devices,
676            active_bottlenecks: self.bottleneck_analysis.bottlenecks.len(),
677            high_priority_recommendations: high_priority_issues,
678            analysis_window: self.utilization_analysis.analysis_window,
679        }
680    }
681}
682
683/// Performance summary statistics
684#[derive(Debug, Clone)]
685pub struct PerformanceSummary {
686    pub overall_score: f32,
687    pub total_devices: usize,
688    pub active_bottlenecks: usize,
689    pub high_priority_recommendations: usize,
690    pub analysis_window: Duration,
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_timeline_event_creation() {
699        let event = TimelineEvent::new(
700            "test_event".to_string(),
701            crate::DeviceId(0),
702            "test_operation".to_string(),
703            100.0,
704            (64, 1, 1),
705            vec![1024, 2048],
706        );
707
708        assert_eq!(event.event_id, "test_event");
709        assert_eq!(event.device_id, crate::DeviceId(0));
710        assert_eq!(event.memory_usage_mb, 100.0);
711        assert!(event.end_time.is_none());
712    }
713
714    #[test]
715    fn test_timeline_analyzer() {
716        let analyzer = GpuTimelineAnalyzer::new(100);
717
718        let mut event = TimelineEvent::new(
719            "test".to_string(),
720            crate::DeviceId(0),
721            "matrix_multiply".to_string(),
722            50.0,
723            (16, 16, 1),
724            vec![1024],
725        );
726
727        std::thread::sleep(std::time::Duration::from_millis(10));
728        event.complete();
729
730        analyzer.record_event(event);
731
732        let events = analyzer.get_device_events(crate::DeviceId(0), None);
733        assert_eq!(events.len(), 1);
734    }
735
736    #[test]
737    fn test_performance_monitor() {
738        let monitor = MultiGpuPerformanceMonitor::new(100, Duration::from_secs(1));
739
740        let _handle = monitor.start_operation(
741            "test_op".to_string(),
742            crate::DeviceId(0),
743            "test".to_string(),
744            10.0,
745            (64, 1, 1),
746            vec![512],
747        );
748
749        // Handle will be dropped here, completing the operation
750
751        let analysis = monitor.get_performance_analysis(Duration::from_secs(1));
752        assert!(analysis.is_ok());
753    }
754}