oxicuda_launch/trace.rs
1//! Launch telemetry / tracing integration.
2//!
3//! This module provides the [`kernel_launch_span!`] macro that emits a
4//! `tracing` instrumentation span around every kernel launch when the
5//! `tracing` Cargo feature is enabled. When the feature is disabled the
6//! macro expands to a unit expression `()` with zero overhead.
7//!
8//! # Feature gate
9//!
10//! Add `tracing` to the features in `Cargo.toml` to enable live spans:
11//!
12//! ```toml
13//! [dependencies]
14//! oxicuda-launch = { version = "*", features = ["tracing"] }
15//! ```
16//!
17//! # Span fields
18//!
19//! When enabled, each span carries:
20//!
21//! | Field | Type | Description |
22//! |----------|----------|------------------------------------------|
23//! | `kernel` | `&str` | Function name as passed to `Kernel::new` |
24//! | `grid` | Debug | Grid dimensions `(x, y, z)` |
25//! | `block` | Debug | Block dimensions `(x, y, z)` |
26//!
27//! # Example (with tracing enabled)
28//!
29//! ```rust
30//! use oxicuda_launch::trace::kernel_launch_span;
31//!
32//! let _span = kernel_launch_span!("my_kernel", (4u32, 1u32, 1u32), (256u32, 1u32, 1u32));
33//! // The span is entered while `_span` is in scope.
34//! ```
35
36// =========================================================================
37// kernel_launch_span! macro
38// =========================================================================
39
40/// Emit a tracing span for a kernel launch.
41///
42/// # Arguments
43///
44/// * `$name` — kernel function name (string literal or `&str`).
45/// * `$grid` — grid dimensions (anything that implements `Debug`).
46/// * `$block` — block dimensions (anything that implements `Debug`).
47///
48/// # Behaviour
49///
50/// * With the `tracing` feature: returns an [`tracing::Span`] entered at
51/// `INFO` level. Assign the result to a variable; the span closes when
52/// the variable is dropped.
53/// * Without the `tracing` feature: expands to `()`.
54#[cfg(feature = "tracing")]
55#[macro_export]
56macro_rules! kernel_launch_span {
57 ($name:expr, $grid:expr, $block:expr) => {
58 tracing::info_span!(
59 "kernel_launch",
60 kernel = $name,
61 grid = ?$grid,
62 block = ?$block,
63 )
64 };
65}
66
67/// No-op version used when the `tracing` feature is disabled.
68#[cfg(not(feature = "tracing"))]
69#[macro_export]
70macro_rules! kernel_launch_span {
71 ($name:expr, $grid:expr, $block:expr) => {
72 ()
73 };
74}
75
76// Re-export so callers can use `oxicuda_launch::trace::kernel_launch_span`.
77pub use kernel_launch_span;
78
79// =========================================================================
80// KernelSpanGuard — RAII wrapper that enters/exits the span
81// =========================================================================
82
83/// RAII guard that enters a kernel-launch tracing span and exits on drop.
84///
85/// When the `tracing` feature is disabled this struct is zero-sized.
86///
87/// # Usage
88///
89/// ```rust
90/// use oxicuda_launch::trace::KernelSpanGuard;
91///
92/// let _guard = KernelSpanGuard::enter("vector_add", (4u32, 1u32, 1u32), (256u32, 1u32, 1u32));
93/// // Span active here — dropped at end of scope.
94/// ```
95#[must_use = "The span is closed when the guard is dropped; don't discard it."]
96pub struct KernelSpanGuard {
97 #[cfg(feature = "tracing")]
98 _entered: tracing::span::EnteredSpan,
99}
100
101impl KernelSpanGuard {
102 /// Enter a new kernel-launch span.
103 ///
104 /// The span is active (entered) until this guard is dropped.
105 pub fn enter<G, B>(kernel_name: &str, grid: G, block: B) -> Self
106 where
107 G: std::fmt::Debug,
108 B: std::fmt::Debug,
109 {
110 #[cfg(feature = "tracing")]
111 {
112 let span = tracing::info_span!(
113 "kernel_launch",
114 kernel = kernel_name,
115 grid = ?grid,
116 block = ?block,
117 );
118 Self {
119 _entered: span.entered(),
120 }
121 }
122
123 #[cfg(not(feature = "tracing"))]
124 {
125 // Suppress "unused variable" warnings in no-tracing builds.
126 let _ = kernel_name;
127 let _ = grid;
128 let _ = block;
129 Self {}
130 }
131 }
132}
133
134// =========================================================================
135// Tests
136// =========================================================================
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 /// KernelSpanGuard must be zero-sized when tracing is disabled, or a
143 /// reasonable size when enabled.
144 #[test]
145 fn test_kernel_span_guard_size() {
146 // Zero bytes without tracing; non-zero with tracing (contains EnteredSpan).
147 #[cfg(feature = "tracing")]
148 assert!(std::mem::size_of::<KernelSpanGuard>() > 0);
149
150 #[cfg(not(feature = "tracing"))]
151 assert_eq!(std::mem::size_of::<KernelSpanGuard>(), 0);
152 }
153
154 #[test]
155 fn test_kernel_span_guard_drop() {
156 // Should not panic when dropped.
157 {
158 let _guard =
159 KernelSpanGuard::enter("test_kernel", (1u32, 1u32, 1u32), (32u32, 1u32, 1u32));
160 }
161 // If we reach here, no panic occurred.
162 }
163
164 #[test]
165 fn test_kernel_span_guard_enter_and_exit() {
166 let guard = KernelSpanGuard::enter("matmul", (16u32, 16u32, 1u32), (16u32, 16u32, 1u32));
167 // Guard is active; doing some work.
168 let _ = 1 + 1;
169 drop(guard); // Span exited here.
170 }
171
172 #[test]
173 fn test_macro_invocation_compiles() {
174 // Verify the macro expands without error.
175 let _span = kernel_launch_span!("test_kernel", (4u32, 1u32, 1u32), (256u32, 1u32, 1u32));
176 // On tracing builds _span is a Span; on no-tracing builds it is ().
177 let _ = _span;
178 }
179}