Skip to content

feat(nvtx): add quent-nvtx-events — verbatim NVTX event vocabulary - #386

Merged
rapids-bot[bot] merged 1 commit into
rapidsai:mainfrom
9prady9:nvtx-events
Jul 15, 2026
Merged

feat(nvtx): add quent-nvtx-events — verbatim NVTX event vocabulary#386
rapids-bot[bot] merged 1 commit into
rapidsai:mainfrom
9prady9:nvtx-events

Conversation

@9prady9

@9prady9 9prady9 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

quent-nvtx-events — verbatim NVTX event vocabulary

First of three PRs splitting the NVTX capture foundation out of #385 (per the request to split it up). Stack:

  1. quent-nvtx-events — this PR (vocabulary types)
  2. quent-nvtx-injection — cdylib NVTX loads via NVTX_INJECTION64_PATH (follows once this lands)
  3. quent-nvtx — bridge + deterministic test app + capture e2e (follows injection)

What this adds

A Quent-agnostic, verbatim vocabulary of NVTX events — the raw shapes NVTX hands us, with no interpretation:

  • NvtxEvent covering the core surface: push/pop + start/end ranges, marks, domain lifecycle, registered strings, category/thread naming, resource create/destroy.
  • NvtxMessage / NvtxColor / NvtxEventAttributes, and the core nvtxEventAttributes payload union captured undecoded (NvtxPayload).
  • Handle resolution and payload decoding are deliberately not done here — a later analysis stage resolves handles from the event stream.

Pure vocabulary, separable by design

  • No Quent-internal dependencies. serde is the only dependency and it's optional (default-on serde feature), so a consumer that only needs the in-memory types doesn't pull it — compiles with and without the feature.
  • No pipeline concerns here. Entity naming and the event wrapper live in the bridge crate (PR 3), not in this crate — keeping it a clean, upstreamable data vocabulary.

Re upstreaming the generic vocabulary into nvtx / nvtx-sys: agreed in principle and being pursued in parallel — the crate is kept dependency-free precisely so that move is clean. nvtx-sys is currently producer-only (no consumer/subscriber surface), so upstreaming is a parallel track rather than a blocker for landing capture.

Tests

None — the crate is pure serde-derived data types, so there's nothing crate-specific to assert beyond what serde itself guarantees (round-trip tests removed per review).

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new quent-nvtx-events workspace crate defines optional-serde NVTX event, attribute, and payload vocabulary types. It preserves raw handles, identifiers, and payload values without capture-time resolution or decoding.

Changes

NVTX events crate

Layer / File(s) Summary
Workspace crate wiring
Cargo.toml, integrations/nvtx/events/Cargo.toml
Declares the new crate, includes it in workspace and default-member lists, and configures the default serde feature.
Attribute and payload contracts
integrations/nvtx/events/src/attributes.rs, integrations/nvtx/events/src/payload.rs
Adds NVTX attribute, message, scalar payload, and deferred payload-extension types that retain raw captured values.
Event vocabulary and exports
integrations/nvtx/events/src/lib.rs
Wires modules, re-exports vocabulary types, and defines serde-feature-gated variants for core NVTX events.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the new quent-nvtx-events crate and its verbatim NVTX event vocabulary.
Description check ✅ Passed The description explains the PR purpose, scope, and testing, though the Related Issues section is not filled in.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
integrations/nvtx/events/src/payload.rs (1)

138-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend PayloadExtensionEvent round-trip test to cover all three variants.

Only BinaryPayload is exercised; SchemaRegister and EnumRegister (both carrying Vec<u8> descriptors) are untested, unlike the scalar payload test above which covers every NvtxPayloadValue variant individually.

♻️ Suggested extension
     #[test]
     fn deferred_payload_extension_vocabulary_round_trips() {
-        let event = PayloadExtensionEvent::BinaryPayload {
-            schema_id: 9,
-            bytes: vec![1, 2, 3, 4],
-        };
-        let json = serde_json::to_string(&event).expect("serialize");
-        let back: PayloadExtensionEvent = serde_json::from_str(&json).expect("deserialize");
-        assert_eq!(back, event);
+        let events = [
+            PayloadExtensionEvent::SchemaRegister {
+                domain: 1,
+                schema_id: 2,
+                descriptor: vec![9, 9],
+            },
+            PayloadExtensionEvent::EnumRegister {
+                domain: 1,
+                enum_id: 3,
+                descriptor: vec![8, 8],
+            },
+            PayloadExtensionEvent::BinaryPayload {
+                schema_id: 9,
+                bytes: vec![1, 2, 3, 4],
+            },
+        ];
+        for event in &events {
+            let json = serde_json::to_string(event).expect("serialize");
+            let back: PayloadExtensionEvent = serde_json::from_str(&json).expect("deserialize");
+            assert_eq!(&back, event);
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/nvtx/events/src/payload.rs` around lines 138 - 147, Extend
deferred_payload_extension_vocabulary_round_trips to serialize, deserialize, and
assert equality for SchemaRegister and EnumRegister in addition to
BinaryPayload, covering all three PayloadExtensionEvent variants and their
Vec<u8> descriptors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@integrations/nvtx/events/src/payload.rs`:
- Around line 138-147: Extend deferred_payload_extension_vocabulary_round_trips
to serialize, deserialize, and assert equality for SchemaRegister and
EnumRegister in addition to BinaryPayload, covering all three
PayloadExtensionEvent variants and their Vec<u8> descriptors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: fab4c554-75ed-48b4-862b-108fd2bedb63

📥 Commits

Reviewing files that changed from the base of the PR and between f31e60b and 7f9ac43.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (5)
  • Cargo.toml
  • integrations/nvtx/events/Cargo.toml
  • integrations/nvtx/events/src/attributes.rs
  • integrations/nvtx/events/src/lib.rs
  • integrations/nvtx/events/src/payload.rs

@johanpel johanpel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, some comments:

Comment thread integrations/nvtx/events/src/attributes.rs Outdated
Comment thread integrations/nvtx/events/src/attributes.rs Outdated
Comment thread integrations/nvtx/events/src/lib.rs Outdated
//! analyzer resolves registered strings, domains, and categories from the event
//! stream in a later phase.

use serde::{Deserialize, Serialize};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to feature gate serde support since not all exporters require it since #250 was solved

Comment thread integrations/nvtx/events/src/lib.rs Outdated
Comment thread integrations/nvtx/events/src/lib.rs Outdated
Comment thread integrations/nvtx/events/src/lib.rs Outdated
Comment thread integrations/nvtx/events/src/lib.rs Outdated
Comment thread integrations/nvtx/events/src/lib.rs Outdated
Comment thread integrations/nvtx/events/src/payload.rs Outdated
A Quent-agnostic, verbatim vocabulary of NVTX events: the `NvtxEvent` enum
covering the core NVTX surface (push/pop + start/end ranges, marks, domain
lifecycle, registered strings, category/thread naming, resource
create/destroy), `NvtxMessage`/`NvtxColor`/`NvtxEventAttributes`, and the core
`nvtxEventAttributes` payload union captured undecoded (`NvtxPayload`).

Pure vocabulary: no Quent-internal dependencies, and `serde` is optional
(behind a default-on `serde` feature), so the crate stays cleanly separable
and can be offered upstream to the NVTX Rust crates later. Adapting these
events into Quent's pipeline (entity naming, the event wrapper) is the bridge
crate's concern, not this crate's.

First of three stacked crate PRs for NVTX capture (events -> injection ->
bridge).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Pradeep Garigipati <pgarigipati@nvidia.com>
@johanpel

Copy link
Copy Markdown
Contributor

From a code perspective this looks fine to me but I think we need to reconsider adding it as a Quent crate and follow the suggestion here: #385 (comment)

@9prady9

9prady9 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

From a code perspective this looks fine to me but I think we need to reconsider adding it as a Quent crate and follow the suggestion here: #385 (comment)

I think we should keep this and look into that in parallel. There is no need to block the follow tasks and pursue this which can be delayed with fork creation and setting up other things.

Keeping this as a separate crate lets us remove this at later point quickly and switch over to the nvtx-sys upstream.

@mbrobbel

Copy link
Copy Markdown
Member

From a code perspective this looks fine to me but I think we need to reconsider adding it as a Quent crate and follow the suggestion here: #385 (comment)

I think we should keep this and look into that in parallel. There is no need to block the follow tasks and pursue this which can be delayed with fork creation and setting up other things.

Keeping this as a separate crate lets us remove this at later point quickly and switch over to the nvtx-sys upstream.

I'm curious, with a fork we are not blocked, so why not do that now?

@9prady9

9prady9 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

From a code perspective this looks fine to me but I think we need to reconsider adding it as a Quent crate and follow the suggestion here: #385 (comment)

I think we should keep this and look into that in parallel. There is no need to block the follow tasks and pursue this which can be delayed with fork creation and setting up other things.
Keeping this as a separate crate lets us remove this at later point quickly and switch over to the nvtx-sys upstream.

I'm curious, with a fork we are not blocked, so why not do that now?

If and when I do any changes to these crates, one would have to jump through couple of extra steps to achieve that. Instead if move/upstream them later, I can play with them quickly and upstream them once they are upstream ready. Potentially less work for me with the trade off being some extra bunch of lines in the repository temporarily.

I am confident it won't stay in the same shape/form as it is today given how things change as we progress through next 4 phases listed as sub-issues in #76

@johanpel johanpel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If and when I do any changes to these crates, one would have to jump through couple of extra steps to achieve that. Instead if move/upstream them later, I can play with them quickly and upstream them once they are upstream ready. Potentially less work for me with the trade off being some extra bunch of lines in the repository temporarily.

To make sure we are on the same page (perhaps this is clear already), the suggestion of @mbrobbel is to create a personal fork NVTX, add the Rust injection wrappers there, then use a Cargo git dependency.

You can iterate quickly on the fork without upstreaming anything to NVTX until you feel it's done. Only takes a bump of the git rev to synchronize changes. Less work in the end; when you're ready to upstream to NVTX, just open a PR from the fork, get it merged and update the git dep, no code migration needed. And you can leverage NVTX' existing build script to generate sys bindings.

Ultimately your choice, approving to unblock merging using the current approach if you prefer that.

@9prady9

9prady9 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

If and when I do any changes to these crates, one would have to jump through couple of extra steps to achieve that. Instead if move/upstream them later, I can play with them quickly and upstream them once they are upstream ready. Potentially less work for me with the trade off being some extra bunch of lines in the repository temporarily.

To make sure we are on the same page (perhaps this is clear already), the suggestion of @mbrobbel is to create a personal fork NVTX, add the Rust injection wrappers there, then use a Cargo git dependency.

You can iterate quickly on the fork without upstreaming anything to NVTX until you feel it's done. Only takes a bump of the git rev to synchronize changes. Less work in the end; when you're ready to upstream to NVTX, just open a PR from the fork, get it merged and update the git dep, no code migration needed. And you can leverage NVTX' existing build script to generate sys bindings.

Ultimately your choice, approving to unblock merging using the current approach if you prefer that.

That's true, it shifts the task to host this code temporally. However, I have to shift to different repository (locally of course) whenever I want edit the code from those crates. Yes, it is few simple steps, nevertheless some extra hops to achieve it compared to just editing the file in the sibling folder.

Hence my hesitation to do it right away. Lets do it in place and handle the shifting part later.

@9prady9

9prady9 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit cd46014 into rapidsai:main Jul 15, 2026
12 checks passed
@9prady9
9prady9 deleted the nvtx-events branch July 15, 2026 07:48
@9prady9

9prady9 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Created ticket for upstreaming task #388

rapids-bot Bot pushed a commit that referenced this pull request Jul 17, 2026
## `quent-nvtx-injection` — the NVTX injection cdylib

PR 2 of 3, following #386 (`quent-nvtx-events`, merged). The bridge + capture e2e follows this one.

### What it is

- The Quent-agnostic cdylib NVTX loads via `NVTX_INJECTION64_PATH`. Exports `InitializeInjectionNvtx2`, fills the CORE/CORE2 callback tables, and converts each callback into the `quent-nvtx-events` vocabulary **verbatim** (raw handles, no resolution/decoding).
- Depends on nothing Quent-internal except `quent-nvtx-events`. Linux 64-bit only (`compile_error!` elsewhere).

### Safety & correctness

- **Panic containment** — every `extern "C"` callback runs inside `catch_unwind`; a panic never crosses the C ABI. Synthesized handles/ids and push/pop levels are returned correctly even if conversion panics.
- **Per-image init** — NVTX calls `InitializeInjectionNvtx2` once per NVTX-using image (exe + each instrumented `.so` has its own `nvtxGlobals`), so callbacks are installed on **every** call, not just the first — otherwise later images are silently no-op'd.
- **Bounded reads** — attribute structs are read only up to their declared `size`; the payload union is read at the tagged member's exact width (no uninitialized upper bytes).
- **String copy-in** — immediate `const char*` messages are copied into owned `String`s before returning; registered strings keep only their raw handle.
- **Faithful push/pop** — range push/pop return NVTX's 0-based per-thread, per-domain nesting level.
- **Bounds-checked table writes** — callbacks are written only into in-range, non-null NVTX slots.

### Generated bindings

- `build.rs` runs `bindgen` over the NVTX ABI into `$OUT_DIR` on every build — nothing checked in, no `bindings.rs` to keep in sync (resource/range-id types folded into the allowlist; no hand-written `convert::abi`).
- Headers + `libclang` come from the pixi-pinned `nvtx-c` / `libclang` packages (`[target.linux-64.dependencies]`), so `pixi run cargo …` is hermetic (`build.rs` reads `CONDA_PREFIX`). Bumping the captured NVTX version is a one-line `nvtx-c` bump — no regen feature, NVTX git dep, or `deny.toml` allow-git.

### Captured surface

- **CORE2 (domain-scoped ASCII):** mark, range start/end/push/pop, domain / register-string / name-category / resource.
- **CORE (classic default-domain ASCII):** `nvtxMarkA`/`Ex`, `nvtxRangePushA`/`Ex`, `nvtxRangePop`, `nvtxRangeStartA`/`Ex`/`nvtxRangeEnd`, `nvtxNameCategoryA`, `nvtxNameOsThreadA` — captured on the default domain (`0`). No vocabulary change: `quent-nvtx-events` already models the default domain as `domain 0`.
- **Wide-char (`*W`/Unicode):** subscribed with warn-once stubs — Unicode capture deferred, but such a call emits a one-time diagnostic and keeps range nesting/ids valid instead of silently no-op'ing.

### Tests

- 17 unit tests covering conversion and nesting logic (verbatim capture per kind, size-bounded reads, string copy-in, member-width payloads, per-thread/per-domain nesting, default-domain `*A` conversions). No NVTX headers or GPU required beyond the pixi-pinned `nvtx-c`.

### Upstreaming

- The injection ABI allowlist / consumer surface is a candidate to contribute to `nvtx-sys` behind a `tools` feature (currently producer-only) — parallel track, not a blocker.

Authors:
  - Pradeep Garigipati (https://github.com/9prady9)

Approvers:
  - Johan Peltenburg (https://github.com/johanpel)

URL: #391
9prady9 added a commit to 9prady9/quent that referenced this pull request Jul 17, 2026
PR 3 of 3, completing NVTX capture on top of rapidsai#386 (quent-nvtx-events) and
rapidsai#391 (quent-nvtx-injection). `quent-nvtx` is the bridge that adapts each
captured NvtxEvent into Quent's event pipeline and builds the
self-configuring capture cdylib NVTX loads via NVTX_INJECTION64_PATH.

- rlib: fronts Quent's unbounded EventSender with a bounded, lock-free ring
  (drop-and-count on overflow, surfaced at teardown); a drain thread forwards
  the ring to an exporter. The injection hook stamps a capture timestamp and
  enqueues without blocking the app thread.
- cdylib: an ELF .init_array constructor reads QUENT_NVTX_OUTPUT_DIR (+ an
  optional QUENT_NVTX_SESSION), builds an ndjson pipeline, and installs the
  hook in the same image whose callbacks NVTX invokes; a .fini_array
  destructor flushes at exit.

NvtxEventEntity is a transparent newtype over NvtxEvent that implements the
pipeline's quent_events::EntityEvent (NAME = "NvtxEvent"); the vocabulary
crate stays Quent-agnostic, so the newtype adapts it here.

The e2e feature builds a deterministic, self-contained NVTX emitter
(nvtx_test_app, no GPU) and a subprocess harness that attaches the cdylib and
asserts every core NVTX kind, the CORE payload union, cross-thread range
pairing, and per-thread naming round-trip through ndjson. NVTX client headers
come from the pixi-pinned nvtx-c package ($CONDA_PREFIX/include), matching the
injection crate; no NVTX git dep.

Registered in workspace members only (not default-members), Linux 64-bit only.

Signed-off-by: Pradeep Garigipati <pgarigipati@nvidia.com>
9prady9 added a commit to 9prady9/quent that referenced this pull request Jul 20, 2026
… e2e)

PR 3 of 3, on rapidsai#386 (events) + rapidsai#391 (injection). Adapts each captured
NvtxEvent into Quent's pipeline; the self-configuring cdylib NVTX loads via
NVTX_INJECTION64_PATH.

- Hook stamps each event and pushes to a bounded, lock-free ring
  (drop-and-count on overflow); a drain thread forwards to an ndjson exporter.
- cdylib self-configures from QUENT_NVTX_OUTPUT_DIR at load, flushes at exit.
- NvtxEventEntity: transparent newtype over NvtxEvent implementing EntityEvent.
- e2e: one multi-threaded C++ producer (domain-scoped, mimics libcudf) run
  under the cdylib; asserts every core kind round-trips through ndjson.

Signed-off-by: Pradeep Garigipati <pgarigipati@nvidia.com>
9prady9 added a commit to 9prady9/quent that referenced this pull request Jul 22, 2026
… e2e)

PR 3 of 3, on rapidsai#386 (events) + rapidsai#391 (injection). Adapts each captured
NvtxEvent into Quent's pipeline; the self-configuring cdylib NVTX loads via
NVTX_INJECTION64_PATH.

- Hook stamps each event and pushes to a bounded, lock-free ring
  (drop-and-count on overflow); a drain thread forwards to an ndjson exporter.
- cdylib self-configures from QUENT_NVTX_OUTPUT_DIR at load, flushes at exit.
- NvtxEventEntity: transparent newtype over NvtxEvent implementing EntityEvent.
- e2e: one multi-threaded C++ producer (domain-scoped, mimics libcudf) run
  under the cdylib; asserts every core kind round-trips through ndjson.

Signed-off-by: Pradeep Garigipati <pgarigipati@nvidia.com>
rapids-bot Bot pushed a commit that referenced this pull request Jul 22, 2026
Wires captured NVTX events into a Quent pipeline, completing the foundational NVTX stack (#386 `quent-nvtx-events`, #391 `quent-nvtx-injection`).

### What it is

The application drives capture: it owns its Quent `Context` and exporter, annotates its code with the NVTX Rust API, and links `quent-nvtx-injection` with its `static-injection` feature so NVTX initializes injection in-process at the first NVTX call — no cdylib, no `NVTX_INJECTION64_PATH`.

- **`quent-nvtx-bridge`** — `NvtxEventEntity`, a `#[serde(transparent)]` newtype over `NvtxEvent` implementing Quent's `EntityEvent` (the orphan-rule adapter; the only crate depending on Quent internals).
- **`quent-instrumentation`** — adds `Observer::sender()`, a cloned `EventSender` so the `'static` injection hook can emit into an app-owned observer that still flushes on drop.
- **`quent-nvtx-injection`** — `static-injection` links the strong-symbol shim with `+whole-archive`, so the strong `InitializeInjectionNvtx2` overrides NVTX's weak no-op.
- **`quent-nvtx-example`** — runnable wiring plus its test.

### Using it

```rust
let observer = ctx.block_on(async { ctx.observer::<NvtxEventEntity>(opts).await })?;
let sender = observer.sender();
quent_nvtx_injection::install_hook(move |e| {
    sender.send(Event::new_now(session, NvtxEventEntity::from(e)));
})?;
// ... nvtx::mark!(...), nvtx::range!(...) ...
drop(observer); // flush
```

### Tests

`cargo test -p quent-nvtx-example` runs the example against a temp dir and asserts every core NVTX kind round-trips through ndjson. No GPU. Linux 64-bit only.

Authors:
  - Pradeep Garigipati (https://github.com/9prady9)

Approvers:
  - Johan Peltenburg (https://github.com/johanpel)

URL: #402
@9prady9 9prady9 mentioned this pull request Jul 22, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants