Skip to content

feat(nvtx): add UI viewport contracts - #562

Merged
rapids-bot[bot] merged 4 commits into
rapidsai:mainfrom
9prady9:pr/nvtx-ui-contracts
Aug 12, 2026
Merged

feat(nvtx): add UI viewport contracts#562
rapids-bot[bot] merged 4 commits into
rapidsai:mainfrom
9prady9:pr/nvtx-ui-contracts

Conversation

@9prady9

@9prady9 9prady9 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the Rust-owned NVTX exchange contract used by the server and Timeline UI.

The new nvtx-ui crate:

  • defines catalog, selection, viewport, lane, item, color, and statistics types
  • validates and canonicalizes domain/category selections
  • converts reconstructed NVTX models into deterministic, UI-ready viewport responses
  • owns filtering, lane grouping, depth, ordering, colors, clipping, and statistics

Related to #373

Verification

pixi run cargo test -p nvtx-ui --locked
pixi run cargo clippy -p nvtx-ui --all-targets --locked -- -D warnings
pixi run cargo fmt --all -- --check

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds the nvtx-ui Rust package to the workspace. It defines serializable NVTX contracts, validates viewport requests, converts model data into lanes, computes statistics, and tests JSON and TypeScript representations.

Changes

NVTX UI model layer

Layer / File(s) Summary
Package contracts and catalog validation
Cargo.toml, integrations/nvtx/ui/Cargo.toml, integrations/nvtx/ui/src/lib.rs, deny.toml
Adds the nvtx-ui workspace package and serializable catalog and viewport contracts. Catalog construction, selection generation, request canonicalization, validation, and anomaly reporting are implemented and tested.
Viewport conversion and lane mapping
integrations/nvtx/ui/src/lib.rs
Converts selected NVTX ranges and marks into clipped, ordered, depth-aware lanes. It preserves observed bounds and formats colors. Tests cover clipping, nested lanes, malformed parent chains, colors, and mark placement.
Viewport statistics and TypeScript validation
integrations/nvtx/ui/src/lib.rs
Computes grouped statistics for closed, incomplete, and zero-duration ranges with saturating totals. JSON helpers preserve large u64 values, and TypeScript declaration tests verify string and nullable fields.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • rapidsai/quent#473: Introduces the nvtx-analyzer model data consumed by this UI contract and conversion layer.

Suggested labels: feature request

Suggested reviewers: johanpel, mbrobbel, cmatzenbach

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding NVTX UI viewport contracts.
Description check ✅ Passed The description explains the crate, its responsibilities, and verification; optional issue and screenshot sections are absent.
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.
✨ 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.

Actionable comments posted: 2

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
integrations/nvtx/ui/src/lib.rs-679-685 (1)

679-685: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

min_duration and max_duration report 0 when no range is complete.

If every range in a group is incomplete, accumulate returns at Line 674 before it touches min_duration and max_duration. Both fields stay at their Default value of 0. finish then emits min_duration: 0 and max_duration: 0.

The doc comment at Lines 146-148 states that incomplete ranges never receive an inferred duration. A zero is an inferred duration to any consumer that does not first read observed_count. The test at Lines 915-922 does not cover these two fields.

The contract is new and unreleased. Change the two fields to Option<u64> now, so the TypeScript type carries null and the Timeline UI cannot misread the value.

🛡️ Proposed change
-    pub min_duration: u64,
-    pub max_duration: u64,
+    pub min_duration: Option<u64>,
+    pub max_duration: Option<u64>,
-            min_duration: self.min_duration,
-            max_duration: self.max_duration,
+            min_duration: (self.observed_count > 0).then_some(self.min_duration),
+            max_duration: (self.observed_count > 0).then_some(self.max_duration),

If you keep the u64 fields, add a test that asserts the values for an all-incomplete group, and document the sentinel on the struct.

🤖 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/ui/src/lib.rs` around lines 679 - 685, Change the group
summary’s min_duration and max_duration fields from u64 to Option<u64>,
preserving None when accumulate receives only incomplete ranges and setting
Some(duration) when the first complete range is observed. Update the
initialization, min/max accumulation, finish serialization, and affected tests
or TypeScript bindings so the resulting value is emitted as null rather than 0.
🧹 Nitpick comments (4)
integrations/nvtx/ui/src/lib.rs (4)

554-559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the sorted-and-deduplicated invariant that binary_search depends on.

selected calls binary_search on selection.category_ids. The call returns a wrong result if the vector is not sorted. canonicalize_request sorts the vector at Line 341, and NvtxViewportResponse::from_model always canonicalizes at Line 374, so the invariant holds today. NvtxDomainSelection.category_ids is a public field, so a future entry point that skips canonicalization would silently drop ranges instead of failing.

Add a doc comment that states the precondition.

♻️ Proposed doc comment
+/// Reports whether `category` is selected.
+///
+/// # Preconditions
+///
+/// `selection.category_ids` must be sorted. `NvtxCatalog::canonicalize_request`
+/// establishes this invariant.
 fn selected(selection: &NvtxDomainSelection, category: Option<u32>) -> bool {
🤖 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/ui/src/lib.rs` around lines 554 - 559, Add a doc comment
directly above selected stating that selection.category_ids must be sorted and
deduplicated before calling selected, because the category lookup uses
binary_search. Keep the implementation unchanged.

819-869: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the remaining NvtxViewportError variants.

The test asserts DuplicateDomain and EmptySelection. Four variants have no test: InvalidWindow, UnknownDomain, UnknownCategory, and UncategorizedUnavailable. The test name states that the canonical selection rules are enforced, so the gap is easy to miss in later changes. InvalidWindow matters most, because every clipping computation in range_item and StatisticsAccumulator::accumulate assumes start <= end.

💚 Proposed additional assertions
let inverted = catalog.canonicalize_request(NvtxViewportRequest {
    viewport: NvtxViewportWindow {
        start: 250,
        end: 100,
    },
    selections: vec![],
});
assert!(matches!(inverted, Err(NvtxViewportError::InvalidWindow)));

let unknown_domain = catalog.canonicalize_request(NvtxViewportRequest {
    viewport: canonical.viewport,
    selections: vec![NvtxDomainSelection {
        domain_id: 4242,
        category_ids: vec![3],
        include_uncategorized: false,
    }],
});
assert!(matches!(
    unknown_domain,
    Err(NvtxViewportError::UnknownDomain { domain_id: 4242 })
));

let unknown_category = catalog.canonicalize_request(NvtxViewportRequest {
    viewport: canonical.viewport,
    selections: vec![NvtxDomainSelection {
        domain_id: 2,
        category_ids: vec![4242],
        include_uncategorized: false,
    }],
});
assert!(matches!(
    unknown_category,
    Err(NvtxViewportError::UnknownCategory {
        category_id: 4242,
        ..
    })
));

As per coding guidelines "New Rust components must include accompanying tests."

🤖 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/ui/src/lib.rs` around lines 819 - 869, Extend
canonical_selection_rules_are_enforced to assert all remaining NvtxViewportError
variants: InvalidWindow for an inverted viewport, UnknownDomain for an
unregistered domain, UnknownCategory for an unavailable category, and
UncategorizedUnavailable using a domain without uncategorized support while
requesting it. Keep the existing assertions unchanged and match each error
variant with relevant field values where applicable.

Source: Coding guidelines


413-422: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Two unreachable! calls panic on a SpanKind::Resource value. Both matches rely on the is_range filter at Line 393, which is far from the match site. The shared root cause is that the "this span is a range" guarantee is carried by a separate call instead of by the type. If the analyzer later adds a SpanKind variant that is_range accepts, or if a caller reaches range_item through another path, the server panics on a request thread instead of skipping the span.

  • integrations/nvtx/ui/src/lib.rs#L413-L422: replace the SpanKind::Resource { .. } => unreachable!(...) arm with continue, so an unexpected variant is skipped rather than fatal.
  • integrations/nvtx/ui/src/lib.rs#L604-L608: make range_item return Option<NvtxRangeItem> and return None for a non-range span, or move the range-kind resolution into a helper that returns Option<NvtxRangeKind> and let the caller skip a None.

Either change removes the panic path while keeping the current behavior for every real input.

🤖 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/ui/src/lib.rs` around lines 413 - 422, Remove both panic
paths for unexpected SpanKind::Resource values: in
integrations/nvtx/ui/src/lib.rs lines 413-422, change the Resource arm in the
span-kind match to skip the span with continue; in lines 604-608, update
range_item to return Option<NvtxRangeItem> (or use an equivalent
Option-returning range-kind helper) and have callers skip None. Preserve
existing handling for valid range spans.

522-532: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse domain_order instead of a linear search per statistics group.

The closure runs catalog.domains.iter().find(...) for every statistics group. The group count grows with the number of distinct message and category pairs. The cost is O(groups × domains). domain_order at Line 516 already maps domain_id to an index.

♻️ Proposed refactor
-        let mut statistics = statistics
+        let mut statistics = statistics
             .into_iter()
             .map(|(key, accumulator)| {
-                let domain = catalog
-                    .domains
-                    .iter()
-                    .find(|domain| domain.domain_id == key.domain_id)
-                    .expect("statistics only include catalog domains");
+                let index = *domain_order
+                    .get(&key.domain_id)
+                    .expect("statistics only include catalog domains");
+                let domain = &catalog.domains[index];
                 accumulator.finish(&key, domain, model)
             })
             .collect::<Vec<_>>();

Move the domain_order construction above this block to apply the change.

🤖 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/ui/src/lib.rs` around lines 522 - 532, Update the
statistics transformation around the domain_order construction and the into_iter
map to reuse domain_order for each key.domain_id instead of scanning
catalog.domains with iter().find. Move or retain domain_order creation before
this block, resolve the domain index through that map, and use it to access the
corresponding catalog domain while preserving the existing expect behavior for
unknown domains.
🤖 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.

Inline comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 565-585: Update span_depths to memoize resolved parent-chain
depths and reuse them across spans, while retaining safe handling for missing
parents and cycles. Use the spans collection once, resolve each uncached chain
iteratively, and backfill cached depths from the chain. Add a test covering
several nested push/pop ranges and verifying the resulting depths.
- Around line 207-228: Refactor NvtxCatalog::from_model to build per-domain
thread IDs and uncategorized flags in single passes over model.spans() and
model.marks() before mapping model.domains(), then look up each domain by
domain.domain while preserving current categorization behavior. Avoid rescanning
spans or marks inside the domain closure; treat the optional catalog-caching
entry point for NvtxViewportResponse::from_model as a separate consideration
unless required by the existing design.

---

Other comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 679-685: Change the group summary’s min_duration and max_duration
fields from u64 to Option<u64>, preserving None when accumulate receives only
incomplete ranges and setting Some(duration) when the first complete range is
observed. Update the initialization, min/max accumulation, finish serialization,
and affected tests or TypeScript bindings so the resulting value is emitted as
null rather than 0.

---

Nitpick comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 554-559: Add a doc comment directly above selected stating that
selection.category_ids must be sorted and deduplicated before calling selected,
because the category lookup uses binary_search. Keep the implementation
unchanged.
- Around line 819-869: Extend canonical_selection_rules_are_enforced to assert
all remaining NvtxViewportError variants: InvalidWindow for an inverted
viewport, UnknownDomain for an unregistered domain, UnknownCategory for an
unavailable category, and UncategorizedUnavailable using a domain without
uncategorized support while requesting it. Keep the existing assertions
unchanged and match each error variant with relevant field values where
applicable.
- Around line 413-422: Remove both panic paths for unexpected SpanKind::Resource
values: in integrations/nvtx/ui/src/lib.rs lines 413-422, change the Resource
arm in the span-kind match to skip the span with continue; in lines 604-608,
update range_item to return Option<NvtxRangeItem> (or use an equivalent
Option-returning range-kind helper) and have callers skip None. Preserve
existing handling for valid range spans.
- Around line 522-532: Update the statistics transformation around the
domain_order construction and the into_iter map to reuse domain_order for each
key.domain_id instead of scanning catalog.domains with iter().find. Move or
retain domain_order creation before this block, resolve the domain index through
that map, and use it to access the corresponding catalog domain while preserving
the existing expect behavior for unknown domains.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 2733b7b4-01d1-4e90-83b1-2f1f14b76436

📥 Commits

Reviewing files that changed from the base of the PR and between 5817484 and 562c6de.

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

Comment thread integrations/nvtx/ui/src/lib.rs Outdated
Comment thread integrations/nvtx/ui/src/lib.rs

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 30-35: Update all six counter fields in
NvtxCatalogAnomalies—orphan_range_ends, orphan_range_pops,
orphan_resource_destroys, reused_range_ids, reused_resource_handles, and
total—to use decimal_u64 Serde serialization and TypeScript string types. Add
JSON and TypeScript declaration assertions covering u64::MAX for these fields.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 3c1e0082-cb01-434b-ba7a-78ccf27f312f

📥 Commits

Reviewing files that changed from the base of the PR and between 562c6de and 406ec5e.

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

Comment thread integrations/nvtx/ui/src/lib.rs

@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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
integrations/nvtx/ui/src/lib.rs-676-678 (1)

676-678: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat an invalid parent reference as a root span.

When parent_index >= spans.len(), Line 677 sets next_depth to 1. The span then renders in a depth-1 lane even though no parent exists. Leave next_depth at 0 for this case. Add a test for an out-of-range SpanId.

Proposed fix
             if index >= spans.len() {
-                next_depth = 1;
                 break;
             }

Also applies to: 701-705

🤖 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/ui/src/lib.rs` around lines 676 - 678, Update the invalid
parent-reference handling in the span depth calculation to keep next_depth at 0
when the referenced index is out of range, so the span renders as a root. Apply
the same change to the corresponding logic near the other reported location, and
add a test covering an out-of-range SpanId.
🤖 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.

Other comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 676-678: Update the invalid parent-reference handling in the span
depth calculation to keep next_depth at 0 when the referenced index is out of
range, so the span renders as a root. Apply the same change to the corresponding
logic near the other reported location, and add a test covering an out-of-range
SpanId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: fd44f618-947a-40e8-b8f7-3cc479a9c610

📥 Commits

Reviewing files that changed from the base of the PR and between 406ec5e and 9f7e2f7.

📒 Files selected for processing (1)
  • integrations/nvtx/ui/src/lib.rs

@9prady9

9prady9 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Viewport contracts: purpose and data flow

The contracts in this PR form one pipeline:

NvtxModel -> NvtxCatalog -> NvtxViewportRequest -> NvtxViewportResponse

In short:

  • Catalog: What exists in the trace?
  • Request: What does the user want to inspect now?
  • Response: What should the UI render and report?

Contract by contract:

  1. NvtxCatalog — stable trace-wide metadata used to initialize filters and navigation without reconstructing the whole viewport.
  2. NvtxCatalogAnomalies — reports orphan closes, reused identifiers, and whether reconstruction is faithful, so the UI can warn when rendered spans or statistics may not tell the full story.
  3. NvtxCatalogDomain — describes each domain and its available threads and categories.
  4. NvtxCatalogThread — identifies the process/thread choices available within a domain.
  5. NvtxCatalogCategory — identifies category choices and their display colors.
  6. NvtxViewportWindow — the requested time interval. Nanosecond values cross JSON as decimal strings to preserve full u64 precision.
  7. NvtxDomainSelection — one selected domain plus its optional thread and category filters.
  8. NvtxViewportRequest — combines the time window with domain selections and defines the exact slice requested by the client.
  9. NvtxViewportResponse — contains the stable catalog plus the lane groups and statistics for that slice.
  10. NvtxDomainLaneGroup — groups renderable lanes under a domain.
  11. NvtxLaneIdentity — states what a lane represents: a thread at a nesting depth, process ranges, or marks.
  12. NvtxLane — a renderable row containing ranges and marks.
  13. NvtxRangeKind — distinguishes thread ranges from process ranges.
  14. NvtxRangeItem — a single visible range. Observed bounds preserve captured truth; display bounds are clipped to the viewport. An incomplete range has no observed end or duration.
  15. NvtxMarkItem — a point event visible in the requested window.
  16. NvtxRangeStatistics — aggregates visible ranges by domain/category/message. count includes incomplete ranges, while duration statistics use completed observed ranges; min/max are optional when no completed duration exists.
  17. NvtxViewportError — explicit validation failures for malformed windows, decimal identifiers, unknown domains, and invalid selections.

Two implementation properties are important:

  • Catalog construction and viewport grouping scan the model once, avoiding work proportional to domains × spans.
  • from_model_with_catalog allows the server to cache and reuse stable catalog metadata across pans and filter changes.

This separation keeps discovery, interaction state, and rendering payloads independent while preserving reconstruction fidelity and lossless timestamps.

Comment thread integrations/nvtx/ui/src/lib.rs Outdated
/// Inclusive viewport bounds in Unix nanoseconds.
#[derive(TS, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NvtxViewportWindow {
#[serde(with = "decimal_u64")]

@johallar johallar Aug 10, 2026

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.

javascript has to rely on bigint for nanosecond epoch timestamps, so we designed the resource timelines to give these start/end timestamps in decimal seconds relative to the query start time to get around that. If possible I would like to design NVTX viewport/range starts and ends to be the same.

Timeline Request:
Image

Timeline response:
Image

@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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
integrations/nvtx/ui/src/lib.rs (2)

447-455: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Couple the cached catalog to its time origin.

from_model_with_catalog accepts catalog and query_start as independent parameters. NvtxCatalog already stores trace_start and trace_end relative to the origin used when the catalog was built. If a caller caches a catalog and later passes a different query_start, the response times use one origin and the cached catalog uses another. No check detects the mismatch.

Store the origin in the catalog and read it here, or document the invariant on the public function.

♻️ Option: derive the origin from the catalog
 pub struct NvtxCatalog {
+    /// Absolute time origin used for every relative-second field.
+    #[serde(skip)]
+    #[ts(skip)]
+    pub query_start: TimeUnixNanoSec,
     /// Trace start in seconds relative to the query start.
     pub trace_start: f64,

Then drop the query_start parameter from from_model_with_catalog and use catalog.query_start.

🤖 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/ui/src/lib.rs` around lines 447 - 455, Update NvtxCatalog
and from_model_with_catalog so the catalog retains the query_start origin used
during construction, then derive the viewport origin from catalog.query_start
instead of accepting an independent query_start parameter. Update callers and
construction paths to preserve this coupled origin and prevent cached catalogs
from being used with mismatched time bases.

690-729: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the cycle and out-of-range parent branches.

The nested-chain test at Lines 1347-1405 exercises the normal path only. Lines 699-702 and Lines 707-714 handle an out-of-range parent index and a parent cycle. Neither branch has a test, and neither is reachable from NvtxModelBuilder output.

Note that an out-of-range parent yields depth 1, not depth 0. A child with an unresolvable parent therefore renders in a "depth 1" lane with no depth 0 lane above it. Confirm that this is the intended fallback, and pin it with a test.

As per coding guidelines "New Rust components must include accompanying tests."

🤖 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/ui/src/lib.rs` around lines 690 - 729, Add focused tests
for the depth-calculation logic covering both an out-of-range parent index and a
cyclic parent chain, using the relevant span-depth helper or test-accessible
path rather than relying on NvtxModelBuilder output. Assert that an unresolvable
parent assigns depth 1 and that cycle members receive depth 0, preserving the
existing nested-chain behavior.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Line 57: Update the response structs NvtxCatalogDomain, NvtxDomainLaneGroup,
NvtxRangeItem, NvtxMarkItem, and NvtxRangeStatistics so each domain_id field
uses decimal_u64 serialization and declares the TypeScript type as string,
matching NvtxDomainSelection. Extend the declaration test to verify domain_id:
string for all five response types.

---

Nitpick comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 447-455: Update NvtxCatalog and from_model_with_catalog so the
catalog retains the query_start origin used during construction, then derive the
viewport origin from catalog.query_start instead of accepting an independent
query_start parameter. Update callers and construction paths to preserve this
coupled origin and prevent cached catalogs from being used with mismatched time
bases.
- Around line 690-729: Add focused tests for the depth-calculation logic
covering both an out-of-range parent index and a cyclic parent chain, using the
relevant span-depth helper or test-accessible path rather than relying on
NvtxModelBuilder output. Assert that an unresolvable parent assigns depth 1 and
that cycle members receive depth 0, preserving the existing nested-chain
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 77577135-d747-47e0-8170-4ad849d609a8

📥 Commits

Reviewing files that changed from the base of the PR and between 9f7e2f7 and f26486b.

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

Comment thread integrations/nvtx/ui/src/lib.rs

@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.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
integrations/nvtx/ui/src/lib.rs-21-25 (1)

21-25: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve query_start when restoring NvtxCatalog.

NvtxCatalog derives Deserialize, so #[serde(skip)] restores query_start as 0. from_model_with_catalog then uses the wrong epoch and can return an empty viewport. Remove Deserialize or require an explicit query_start.

🤖 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/ui/src/lib.rs` around lines 21 - 25, Update the NvtxCatalog
deserialization contract around query_start: do not allow #[serde(skip)] to
restore query_start as zero. Remove Deserialize from NvtxCatalog if catalogs are
not meant to be restored, or require and deserialize an explicit query_start
value so from_model_with_catalog always uses the original epoch.
🤖 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.

Other comments:
In `@integrations/nvtx/ui/src/lib.rs`:
- Around line 21-25: Update the NvtxCatalog deserialization contract around
query_start: do not allow #[serde(skip)] to restore query_start as zero. Remove
Deserialize from NvtxCatalog if catalogs are not meant to be restored, or
require and deserialize an explicit query_start value so from_model_with_catalog
always uses the original epoch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 126fff72-93f7-42f8-8ee5-3e712ec2a31a

📥 Commits

Reviewing files that changed from the base of the PR and between f26486b and ff6bbbe.

📒 Files selected for processing (2)
  • deny.toml
  • integrations/nvtx/ui/src/lib.rs

@9prady9
9prady9 requested a review from johallar August 11, 2026 07:58

@joosthooz joosthooz 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.

Just some small questions, I'm not sure if the serde thing is an actual problem

Comment thread integrations/nvtx/ui/src/lib.rs Outdated
Comment on lines +23 to +25
#[serde(skip)]
#[ts(skip)]
query_start: TimeUnixNanoSec,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why do we skip query_start, wouldn't that cause trouble when roundtripping data?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixing this one.

Comment thread integrations/nvtx/ui/src/lib.rs Outdated
) -> Result<NvtxViewportRequest, NvtxViewportError> {
if !request.viewport.start.is_finite()
|| !request.viewport.end.is_finite()
|| request.viewport.start < 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it never possible for a trace to start before the query start, making this negative?

@9prady9 9prady9 Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

From the way I understand these two, query start is the absolute 0.0 as per UI timeline (as per other telemetry info apart from NVTX events) for a given query, while the trace start is set to the minimum time ticker of the earliest/first nvtx event for this run; so I doubt trace_start can appear before query_start.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, that is true only if my assumption that nvtx events are query scoped is true. We might have events before that also in the code. Let me think about this a bit more.

.or_default()
.push(item);
}
SpanKind::StartEnd => domain_items.process_ranges.push(item),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could this result in overlapping ranges, and is that a problem?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, ranges are process wide and can definitely overlap depending on the how the nvtx range API calls are sprinkled around. Right now, data isn't altered but the current render will render rectangles on top of each other when there is a overlap.

It's not a bug but visually hard to grasp that - the only I can think of to resolve this to again stack them in that lane. @johallar what do you recommend here ?

I am not sure how much a visual cue will help though even if we try to solve this rendering overlap. The user can still get the actual time range info from the tooltip.

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.

Let's discuss tomorrow morning a bit, FE can do some intra-lane stacking or try to visualize overlapping spans in the same lane in some other way if i'm understanding correctly.

Comment on lines +313 to +316
threads.sort_by(|left, right| {
left.name
.cmp(&right.name)
.then(left.thread_id.cmp(&right.thread_id))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cosmetic nit; it looks like this would sort threads with default names like thread 1 .. thread 10 like

thread 1
thread 10
thread 2
...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, but there is no real ordering here to those threads than UI arrangement. Unless we want to put them top to bottom going from start time to end of the query time - perhaps that is useful didn' think of that to be honest until now.

@9prady9
9prady9 requested a review from joosthooz August 11, 2026 09:55

@joosthooz joosthooz 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.

Great, thanks

@9prady9

9prady9 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit ceaa049 into rapidsai:main Aug 12, 2026
20 checks passed
@9prady9
9prady9 deleted the pr/nvtx-ui-contracts branch August 12, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants