dynamo_runtime/nvtx.rs
1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! NVTX timeline-annotation helpers for Nsight Systems profiling.
5//!
6//! Delegates to [`cudarc::nvtx`] for the actual NVTX calls
7//!
8//! # Gating (two-level)
9//!
10//! | Cargo feature `nvtx` | `DYN_ENABLE_RUST_NVTX` env | Effect |
11//! |----------------------|----------------------------|-------------------------------------------|
12//! | off (default) | any | macros compile to nothing; zero overhead |
13//! | on | unset | one `Relaxed` load per site (~1 ns) |
14//! | on | `1` / `true` / `yes` | cudarc NVTX calls (~50 ns/annotation) |
15//!
16//! # Usage
17//!
18//! ```rust,ignore
19//! let _r = dynamo_nvtx_range!("preprocess.tokenize"); // RAII — pops at scope end
20//! dynamo_nvtx_push!("codec.encode");
21//! dynamo_nvtx_pop!();
22//! dynamo_nvtx_name_thread!("tokio-worker-0");
23//! ```
24//!
25//! # Build
26//!
27//! ```bash
28//! cargo build --profile profiling --features nvtx
29//! ```
30//! Requires `libnvToolsExt.so` at runtime (CUDA Toolkit or NVHPC).
31
32#[cfg(feature = "nvtx")]
33use std::sync::atomic::{AtomicBool, Ordering};
34
35#[cfg(feature = "nvtx")]
36static NVTX_ENABLED: AtomicBool = AtomicBool::new(false);
37
38// ── Public API ───────────────────────────────────────────────────────────────
39
40/// Initialise the NVTX subsystem from the `DYN_ENABLE_RUST_NVTX` environment variable.
41/// Must be called once at runtime startup before any annotation macros fire.
42/// No-op when the `nvtx` Cargo feature is off.
43pub fn init() {
44 #[cfg(feature = "nvtx")]
45 {
46 let enabled = crate::config::env_is_truthy("DYN_ENABLE_RUST_NVTX");
47 NVTX_ENABLED.store(enabled, Ordering::Relaxed);
48 if enabled {
49 tracing::info!("NVTX annotations enabled (DYN_ENABLE_RUST_NVTX)");
50 }
51 }
52}
53
54/// Returns `true` when the `nvtx` feature is compiled in **and** `DYN_ENABLE_RUST_NVTX` is set.
55#[inline(always)]
56pub fn enabled() -> bool {
57 #[cfg(feature = "nvtx")]
58 {
59 return NVTX_ENABLED.load(Ordering::Relaxed);
60 }
61 #[allow(unreachable_code)]
62 false
63}
64
65/// Push an NVTX range onto the calling thread's stack.
66/// No-op (compiled out) when the `nvtx` feature is off.
67#[inline(always)]
68pub fn push_impl(name: &str) {
69 #[cfg(feature = "nvtx")]
70 {
71 if NVTX_ENABLED.load(Ordering::Relaxed) {
72 cudarc::nvtx::result::range_push(name);
73 }
74 }
75 let _ = name;
76}
77
78/// Pop the innermost NVTX range from the calling thread's stack.
79/// No-op (compiled out) when the `nvtx` feature is off.
80#[inline(always)]
81pub fn pop_impl() {
82 #[cfg(feature = "nvtx")]
83 {
84 if NVTX_ENABLED.load(Ordering::Relaxed) {
85 cudarc::nvtx::result::range_pop();
86 }
87 }
88}
89
90/// Name the current OS thread in the Nsight Systems timeline.
91/// No-op (compiled out) when the `nvtx` feature is off.
92#[inline(always)]
93pub fn name_current_thread_impl(name: &str) {
94 #[cfg(feature = "nvtx")]
95 {
96 if NVTX_ENABLED.load(Ordering::Relaxed) {
97 #[cfg(target_os = "linux")]
98 let tid = unsafe { libc::syscall(libc::SYS_gettid) as u32 };
99 #[cfg(not(target_os = "linux"))]
100 let tid = 0u32;
101 cudarc::nvtx::result::name_os_thread(tid, name);
102 }
103 }
104 let _ = name;
105}
106
107// ── RAII guard ───────────────────────────────────────────────────────────────
108
109/// RAII guard that pops an NVTX range when dropped.
110/// Construct with [`dynamo_nvtx_range!`].
111#[cfg(feature = "nvtx")]
112pub struct NvtxRangeGuard {
113 active: bool,
114}
115
116/// Zero-sized no-op guard used when the `nvtx` feature is off.
117#[cfg(not(feature = "nvtx"))]
118pub struct NvtxRangeGuard;
119
120impl NvtxRangeGuard {
121 #[doc(hidden)]
122 pub fn new(name: &str) -> Self {
123 #[cfg(feature = "nvtx")]
124 {
125 let active = NVTX_ENABLED.load(Ordering::Relaxed);
126 if active {
127 cudarc::nvtx::result::range_push(name);
128 }
129 return NvtxRangeGuard { active };
130 }
131 #[cfg(not(feature = "nvtx"))]
132 {
133 let _ = name;
134 NvtxRangeGuard {}
135 }
136 }
137}
138
139#[cfg(feature = "nvtx")]
140impl Drop for NvtxRangeGuard {
141 fn drop(&mut self) {
142 if self.active {
143 cudarc::nvtx::result::range_pop();
144 }
145 }
146}
147
148#[cfg(not(feature = "nvtx"))]
149impl Drop for NvtxRangeGuard {
150 fn drop(&mut self) {}
151}
152
153// ── Macros ───────────────────────────────────────────────────────────────────
154
155/// Push a named NVTX range onto the calling thread's stack.
156/// Zero-cost when the `nvtx` Cargo feature is off.
157#[macro_export]
158macro_rules! dynamo_nvtx_push {
159 ($name:expr) => {
160 $crate::nvtx::push_impl($name)
161 };
162}
163
164/// Pop the innermost NVTX range from the calling thread's stack.
165/// Zero-cost when the `nvtx` Cargo feature is off.
166#[macro_export]
167macro_rules! dynamo_nvtx_pop {
168 () => {
169 $crate::nvtx::pop_impl()
170 };
171}
172
173/// Open a named NVTX range that closes automatically at end of scope.
174///
175/// ```rust,ignore
176/// let _r = dynamo_nvtx_range!("preprocess.tokenize");
177/// // range closes here
178/// ```
179/// Zero-cost when the `nvtx` Cargo feature is off.
180#[macro_export]
181macro_rules! dynamo_nvtx_range {
182 ($name:expr) => {
183 $crate::nvtx::NvtxRangeGuard::new($name)
184 };
185}
186
187/// Annotate the current OS thread in the Nsight Systems timeline.
188/// Zero-cost when the `nvtx` Cargo feature is off.
189#[macro_export]
190macro_rules! dynamo_nvtx_name_thread {
191 ($name:expr) => {
192 $crate::nvtx::name_current_thread_impl($name)
193 };
194}