Skip to main content

trueno_cupti/
lib.rs

1//! trueno-cupti: CUDA Profiling for ComputeBrick Analysis
2//!
3//! Rust bindings for NVIDIA CUPTI (CUDA Profiling Tools Interface).
4//! Enables detailed GPU profiling for cbtop visualization.
5//!
6//! # Features
7//!
8//! - **Activity Tracing**: Kernel execution, memory copies, synchronization
9//! - **Metrics Collection**: SM utilization, warp occupancy, memory throughput
10//! - **Callback API**: Real-time notifications of CUDA operations
11//! - **PC Sampling**: Instruction-level performance analysis
12//!
13//! # Example
14//!
15//! ```no_run,ignore
16//! use trueno_cupti::{Profiler, ActivityKind};
17//!
18//! let profiler = Profiler::new()?;
19//! profiler.enable(ActivityKind::Kernel)?;
20//! profiler.enable(ActivityKind::MemoryCopy)?;
21//!
22//! // Run CUDA workload...
23//!
24//! let records = profiler.flush()?;
25//! for record in records {
26//!     println!("{}: {} ns", record.name, record.duration_ns);
27//! }
28//! ```
29//!
30//! # Hardware Requirements
31//!
32//! - NVIDIA GPU (Compute Capability 3.0+)
33//! - CUDA Toolkit 11.0+ with CUPTI
34//! - Linux (primary), Windows (experimental)
35
36pub mod activity;
37pub mod callback;
38pub mod error;
39pub mod metrics;
40mod sys;
41
42pub use activity::{ActivityKind, ActivityRecord, KernelRecord, MemcpyRecord};
43pub use callback::{CallbackDomain, CallbackId};
44pub use error::{CuptiError, CuptiResult};
45pub use metrics::{MetricId, MetricValue, SmMetrics, WarpMetrics};
46
47use std::sync::atomic::{AtomicBool, Ordering};
48use std::sync::Arc;
49
50/// CUPTI Profiler - main entry point for GPU profiling.
51///
52/// Thread-safe profiler that collects CUDA activity and metrics.
53pub struct Profiler {
54    /// Whether profiling is currently active
55    active: Arc<AtomicBool>,
56    /// Enabled activity kinds
57    enabled_activities: Vec<ActivityKind>,
58    /// Collected records (flushed on request)
59    records: Vec<ActivityRecord>,
60    /// Whether CUPTI is available on this system
61    available: bool,
62}
63
64impl Profiler {
65    /// Create a new profiler instance.
66    ///
67    /// Returns error if CUPTI is not available.
68    pub fn new() -> CuptiResult<Self> {
69        // Check if CUPTI is available
70        let available = Self::check_cupti_available();
71
72        Ok(Self {
73            active: Arc::new(AtomicBool::new(false)),
74            enabled_activities: Vec::new(),
75            records: Vec::new(),
76            available,
77        })
78    }
79
80    /// Check if CUPTI is available on this system.
81    pub fn is_available(&self) -> bool {
82        self.available
83    }
84
85    /// Enable profiling for a specific activity kind.
86    pub fn enable(&mut self, kind: ActivityKind) -> CuptiResult<()> {
87        if !self.available {
88            return Err(CuptiError::NotAvailable);
89        }
90        if !self.enabled_activities.contains(&kind) {
91            self.enabled_activities.push(kind);
92        }
93        Ok(())
94    }
95
96    /// Disable profiling for a specific activity kind.
97    pub fn disable(&mut self, kind: ActivityKind) -> CuptiResult<()> {
98        self.enabled_activities.retain(|k| *k != kind);
99        Ok(())
100    }
101
102    /// Start profiling.
103    pub fn start(&self) -> CuptiResult<()> {
104        if !self.available {
105            return Err(CuptiError::NotAvailable);
106        }
107        self.active.store(true, Ordering::SeqCst);
108        // In real implementation: cuptiActivityEnable for each kind
109        Ok(())
110    }
111
112    /// Stop profiling.
113    pub fn stop(&self) -> CuptiResult<()> {
114        self.active.store(false, Ordering::SeqCst);
115        // In real implementation: cuptiActivityDisable for each kind
116        Ok(())
117    }
118
119    /// Check if profiling is active.
120    pub fn is_active(&self) -> bool {
121        self.active.load(Ordering::SeqCst)
122    }
123
124    /// Flush collected records and return them.
125    pub fn flush(&mut self) -> CuptiResult<Vec<ActivityRecord>> {
126        // In real implementation: cuptiActivityFlushAll
127        Ok(std::mem::take(&mut self.records))
128    }
129
130    /// Get enabled activity kinds.
131    pub fn enabled_activities(&self) -> &[ActivityKind] {
132        &self.enabled_activities
133    }
134
135    /// Check if CUPTI library is available.
136    fn check_cupti_available() -> bool {
137        // Try to find libcupti.so
138        #[cfg(target_os = "linux")]
139        {
140            // Check common CUPTI locations
141            let paths = [
142                "/usr/local/cuda/lib64/libcupti.so",
143                "/usr/lib/x86_64-linux-gnu/libcupti.so",
144                "/opt/cuda/lib64/libcupti.so",
145            ];
146            for path in &paths {
147                if std::path::Path::new(path).exists() {
148                    return true;
149                }
150            }
151            // Also check LD_LIBRARY_PATH
152            if let Ok(ld_path) = std::env::var("LD_LIBRARY_PATH") {
153                for dir in ld_path.split(':') {
154                    let cupti_path = std::path::Path::new(dir).join("libcupti.so");
155                    if cupti_path.exists() {
156                        return true;
157                    }
158                }
159            }
160        }
161        false
162    }
163}
164
165impl Default for Profiler {
166    fn default() -> Self {
167        Self::new().unwrap_or(Self {
168            active: Arc::new(AtomicBool::new(false)),
169            enabled_activities: Vec::new(),
170            records: Vec::new(),
171            available: false,
172        })
173    }
174}
175
176/// Builder for configuring profiler options.
177#[derive(Debug, Default)]
178pub struct ProfilerBuilder {
179    activities: Vec<ActivityKind>,
180    buffer_size: Option<usize>,
181    flush_period_ms: Option<u64>,
182}
183
184impl ProfilerBuilder {
185    /// Create a new profiler builder.
186    pub fn new() -> Self {
187        Self::default()
188    }
189
190    /// Enable an activity kind.
191    #[must_use]
192    pub fn activity(mut self, kind: ActivityKind) -> Self {
193        self.activities.push(kind);
194        self
195    }
196
197    /// Enable kernel profiling.
198    #[must_use]
199    pub fn kernels(self) -> Self {
200        self.activity(ActivityKind::Kernel)
201    }
202
203    /// Enable memory copy profiling.
204    #[must_use]
205    pub fn memcpy(self) -> Self {
206        self.activity(ActivityKind::MemoryCopy)
207    }
208
209    /// Enable synchronization profiling.
210    #[must_use]
211    pub fn sync(self) -> Self {
212        self.activity(ActivityKind::Synchronization)
213    }
214
215    /// Set activity buffer size.
216    #[must_use]
217    pub fn buffer_size(mut self, size: usize) -> Self {
218        self.buffer_size = Some(size);
219        self
220    }
221
222    /// Set automatic flush period in milliseconds.
223    #[must_use]
224    pub fn flush_period_ms(mut self, ms: u64) -> Self {
225        self.flush_period_ms = Some(ms);
226        self
227    }
228
229    /// Build the profiler.
230    pub fn build(self) -> CuptiResult<Profiler> {
231        let mut profiler = Profiler::new()?;
232        for activity in self.activities {
233            profiler.enable(activity)?;
234        }
235        Ok(profiler)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_profiler_creation() {
245        let profiler = Profiler::new();
246        assert!(profiler.is_ok());
247    }
248
249    #[test]
250    fn test_profiler_default() {
251        let profiler = Profiler::default();
252        assert!(!profiler.is_active());
253    }
254
255    #[test]
256    fn test_profiler_builder() {
257        let builder = ProfilerBuilder::new()
258            .kernels()
259            .memcpy()
260            .buffer_size(1024 * 1024);
261
262        let profiler = builder.build();
263        // May fail if CUPTI not available, that's OK
264        if let Ok(p) = profiler {
265            assert!(p.enabled_activities().contains(&ActivityKind::Kernel));
266            assert!(p.enabled_activities().contains(&ActivityKind::MemoryCopy));
267        }
268    }
269
270    #[test]
271    fn test_activity_enable_disable() {
272        let mut profiler = Profiler::default();
273        if profiler.is_available() {
274            assert!(profiler.enable(ActivityKind::Kernel).is_ok());
275            assert!(profiler
276                .enabled_activities()
277                .contains(&ActivityKind::Kernel));
278            assert!(profiler.disable(ActivityKind::Kernel).is_ok());
279            assert!(!profiler
280                .enabled_activities()
281                .contains(&ActivityKind::Kernel));
282        }
283    }
284
285    #[test]
286    fn test_profiler_start_stop() {
287        let profiler = Profiler::default();
288        if profiler.is_available() {
289            assert!(profiler.start().is_ok());
290            assert!(profiler.is_active());
291            assert!(profiler.stop().is_ok());
292            assert!(!profiler.is_active());
293        }
294    }
295}