1use crate::{DeviceId, GpuDevice, UnifiedGpuResult};
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant};
10
11#[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 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 pub fn complete(&mut self) {
54 self.end_time = Some(Instant::now());
55 }
56
57 pub fn cpu_duration(&self) -> Option<Duration> {
59 self.end_time.map(|end| end.duration_since(self.start_time))
60 }
61
62 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 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; 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 pub fn add_metadata(&mut self, key: String, value: String) {
87 self.metadata.insert(key, value);
88 }
89}
90
91pub 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 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 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 pub fn record_event(&self, event: TimelineEvent) {
117 if let Ok(mut events) = self.events.lock() {
118 events.push_back(event);
119
120 while events.len() > self.max_events {
122 events.pop_front();
123 }
124 }
125 }
126
127 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 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 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 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); 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 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 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 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 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 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 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 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 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 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 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 let theoretical_max_threads = 1024; let utilization = (total_threads as f32 / theoretical_max_threads as f32).min(1.0);
347
348 let memory_efficiency = (event.memory_bandwidth_gb_s() / 500.0).min(1.0); 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; }
356 }
357
358 device_efficiency
359 }
360
361 fn generate_optimization_recommendations(
363 &self,
364 bottlenecks: &[PerformanceBottleneck],
365 ) -> Vec<OptimizationRecommendation> {
366 let mut recommendations = Vec::new();
367
368 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 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#[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#[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#[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#[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#[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#[derive(Debug, Clone)]
491pub enum RecommendationPriority {
492 Low,
493 Medium,
494 High,
495 Critical,
496}
497
498#[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
508pub struct MultiGpuPerformanceMonitor {
510 timeline_analyzer: GpuTimelineAnalyzer,
511 monitoring_enabled: bool,
512 analysis_interval: Duration,
513 last_analysis: Instant,
514}
515
516impl MultiGpuPerformanceMonitor {
517 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 pub fn add_device(&self, device: Arc<GpuDevice>) {
529 self.timeline_analyzer.add_device(device);
530 }
531
532 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 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 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 pub fn set_monitoring_enabled(&mut self, enabled: bool) {
584 self.monitoring_enabled = enabled;
585 }
586
587 pub fn should_perform_analysis(&self) -> bool {
589 self.monitoring_enabled && self.last_analysis.elapsed() >= self.analysis_interval
590 }
591}
592
593pub struct OperationHandle<'a> {
595 event: TimelineEvent,
596 monitor: &'a MultiGpuPerformanceMonitor,
597}
598
599impl<'a> OperationHandle<'a> {
600 pub fn add_metadata(&mut self, key: String, value: String) {
602 self.event.add_metadata(key, value);
603 }
604
605 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 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#[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 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 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 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#[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 let analysis = monitor.get_performance_analysis(Duration::from_secs(1));
752 assert!(analysis.is_ok());
753 }
754}