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 the `nvtx` crate, which compiles the header-only NVTX3 shim into
7//! the binary. Nsight Systems attaches through its injection library at launch,
8//! so no libnvToolsExt.so is needed at build or run time.
9//!
10//! # Gating (two-level)
11//!
12//! | Cargo feature `nvtx` | `DYN_ENABLE_RUST_NVTX` env | Effect |
13//! |----------------------|----------------------------|-------------------------------------------|
14//! | off (default) | any | macros compile to nothing; zero overhead |
15//! | on | unset | one `Relaxed` load per site (~1 ns) |
16//! | on | `1` / `true` / `yes` | NVTX calls (~50 ns/annotation) |
17//!
18//! # Usage
19//!
20//! ```rust,ignore
21//! let _r = dynamo_nvtx_range!("preprocess.tokenize"); // RAII — pops at scope end
22//! dynamo_nvtx_push!("codec.encode");
23//! dynamo_nvtx_pop!();
24//! dynamo_nvtx_name_thread!("tokio-worker-0");
25//! ```
26//!
27//! # Build
28//!
29//! ```bash
30//! cargo build --profile profiling --features nvtx
31//! ```
32
33#[cfg(feature = "nvtx")]
34use std::sync::atomic::{AtomicBool, Ordering};
35
36#[cfg(feature = "nvtx")]
37static NVTX_ENABLED: AtomicBool = AtomicBool::new(false);
38
39// ── Public API ───────────────────────────────────────────────────────────────
40
41/// Initialise the NVTX subsystem from the `DYN_ENABLE_RUST_NVTX` environment variable.
42/// Must be called once at runtime startup before any annotation macros fire.
43/// No-op when the `nvtx` Cargo feature is off.
44pub fn init() {
45 #[cfg(feature = "nvtx")]
46 {
47 let enabled = crate::config::env_is_truthy("DYN_ENABLE_RUST_NVTX");
48 NVTX_ENABLED.store(enabled, Ordering::Relaxed);
49 if enabled {
50 tracing::info!("NVTX annotations enabled (DYN_ENABLE_RUST_NVTX)");
51 }
52 }
53}
54
55/// Returns `true` when the `nvtx` feature is compiled in **and** `DYN_ENABLE_RUST_NVTX` is set.
56#[inline(always)]
57pub fn enabled() -> bool {
58 #[cfg(feature = "nvtx")]
59 {
60 return NVTX_ENABLED.load(Ordering::Relaxed);
61 }
62 #[allow(unreachable_code)]
63 false
64}
65
66/// Push an NVTX range onto the calling thread's stack.
67/// No-op (compiled out) when the `nvtx` feature is off.
68#[inline(always)]
69pub fn push_impl(name: &str) {
70 #[cfg(feature = "nvtx")]
71 {
72 if NVTX_ENABLED.load(Ordering::Relaxed) {
73 nvtx::range_push!("{name}");
74 }
75 }
76 let _ = name;
77}
78
79/// Pop the innermost NVTX range from the calling thread's stack.
80/// No-op (compiled out) when the `nvtx` feature is off.
81#[inline(always)]
82pub fn pop_impl() {
83 #[cfg(feature = "nvtx")]
84 {
85 if NVTX_ENABLED.load(Ordering::Relaxed) {
86 nvtx::range_pop!();
87 }
88 }
89}
90
91/// Name the current OS thread in the Nsight Systems timeline.
92/// No-op (compiled out) when the `nvtx` feature is off.
93#[inline(always)]
94pub fn name_current_thread_impl(name: &str) {
95 #[cfg(feature = "nvtx")]
96 {
97 if NVTX_ENABLED.load(Ordering::Relaxed) {
98 nvtx::name_thread!("{name}");
99 }
100 }
101 let _ = name;
102}
103
104// ── RAII guard ───────────────────────────────────────────────────────────────
105
106/// RAII guard that pops an NVTX range when dropped.
107/// Construct with [`dynamo_nvtx_range!`].
108#[cfg(feature = "nvtx")]
109pub struct NvtxRangeGuard {
110 // Process-scoped range id: guards outlive `.await`s, so the drop can run on a
111 // different worker thread than the open, where a thread-local push/pop pair
112 // would close an unrelated range.
113 range: Option<i32>,
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 range = NVTX_ENABLED
126 .load(Ordering::Relaxed)
127 .then(|| nvtx::range_start!("{name}"));
128 NvtxRangeGuard { range }
129 }
130 #[cfg(not(feature = "nvtx"))]
131 {
132 let _ = name;
133 NvtxRangeGuard {}
134 }
135 }
136}
137
138#[cfg(feature = "nvtx")]
139impl Drop for NvtxRangeGuard {
140 fn drop(&mut self) {
141 if let Some(range) = self.range {
142 nvtx::range_end!(range);
143 }
144 }
145}
146
147#[cfg(not(feature = "nvtx"))]
148impl Drop for NvtxRangeGuard {
149 fn drop(&mut self) {}
150}
151
152// ── Macros ───────────────────────────────────────────────────────────────────
153
154/// Push a named NVTX range onto the calling thread's stack.
155/// Zero-cost when the `nvtx` Cargo feature is off.
156#[macro_export]
157macro_rules! dynamo_nvtx_push {
158 ($name:expr) => {
159 $crate::nvtx::push_impl($name)
160 };
161}
162
163/// Pop the innermost NVTX range from the calling thread's stack.
164/// Zero-cost when the `nvtx` Cargo feature is off.
165#[macro_export]
166macro_rules! dynamo_nvtx_pop {
167 () => {
168 $crate::nvtx::pop_impl()
169 };
170}
171
172/// Open a named NVTX range that closes automatically at end of scope.
173///
174/// ```rust,ignore
175/// let _r = dynamo_nvtx_range!("preprocess.tokenize");
176/// // range closes here
177/// ```
178/// Zero-cost when the `nvtx` Cargo feature is off.
179#[macro_export]
180macro_rules! dynamo_nvtx_range {
181 ($name:expr) => {
182 $crate::nvtx::NvtxRangeGuard::new($name)
183 };
184}
185
186/// Annotate the current OS thread in the Nsight Systems timeline.
187/// Zero-cost when the `nvtx` Cargo feature is off.
188#[macro_export]
189macro_rules! dynamo_nvtx_name_thread {
190 ($name:expr) => {
191 $crate::nvtx::name_current_thread_impl($name)
192 };
193}