Skip to content

test: drive prometheus_mp through nixlTelemetry - #2054

Merged
ovidiusm merged 10 commits into
ai-dynamo:mainfrom
e-eygin:nix-1682-prometheus-mp-core-tests
Aug 18, 2026
Merged

ovidiusm merged 10 commits into
ai-dynamo:mainfrom
e-eygin:nix-1682-prometheus-mp-core-tests

Conversation

@e-eygin

@e-eygin e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What?

Covers the prometheus_mp telemetry exporter's core path in gtest by driving it through
nixlTelemetry instead of calling exportEvent directly: the per-metric allowlist keeping a
deactivated metric out, the dropped-events counter accumulating every flush delta, and
conservation of accepted + dropped events when a small staging queue overflows (both the
single-event path and the all-or-none 4-event addXferStats batch).

Tests only; no production code changes.

Why?

Everything this exporter had constructed it directly and called exportEvent, which skips the
core: the staging queue, the drain task, metric gating and the drop accounting were never
exercised for it. NIX-1685 makes prometheus_mp the configured exporter everywhere, and an
exporter should not become a default while its core path is untested.

The allowlist is the case that matters most. Per-metric activation gates producer-side, before
the staging queue, so it should apply identically to any exporter — but nothing proved a
deactivated metric stays out of an mp store, and NIX-1685 would make that a default-path
behaviour. Closes NIX-1682.

How?

These tests need an HTTP scrape and a way to query the parsed result. Three implementations of
that already existed: two hand-rolled ones in gtest (string matching over the raw body) and a
proper loopback client, OpenMetrics parser and series-query layer in the DOCA tests. Rather
than add a fourth, the DOCA one moved to test/metrics/ as the shared toolkit and the gtest
telemetry tests were converted onto it, which deleted more code than the new tests add.
Waiting on an expected end state rather than sleeping a fixed interval is what keeps the new
tests timing-independent, and it also cut suite runtime.

The mp fixtures got the same treatment: four suites each opened their own per-test temp
directory, two of them separately repeating the mode-0700 chmod the exporter needs in order
not to warn (a warning fails these tests), so that rule now lives in one place. Descriptors
are owned by nixl::scopedFd rather than hand-rolled closers.

The allowlist test asserts on the store snapshot as well as on the scrape: an untouched slot
proves the event never reached the exporter, rather than merely not being rendered.

Verified with ./build/test/gtest/gtest --gtest_filter='Mp*:*Prometheus*:*Telemetry*' (88/88,
stable over --gtest_repeat=5) and the DOCA telemetry targets, which exercise the moved
scrape toolkit.

Summary by CodeRabbit

  • Tests
    • Expanded multi-process telemetry coverage for metric filtering, dropped-event accounting, overflow conservation, histogram data, and stale-process cleanup.
    • Improved validation of Prometheus metrics, labels, counters, gauges, and histograms.
    • Added shared test utilities for scraping, parsing, temporary resources, and telemetry setup.
  • Refactor
    • Consolidated telemetry test helpers under a shared metrics-testing framework.
    • Improved socket and file-descriptor cleanup through automatic resource management.

@e-eygin
e-eygin requested a review from a team as a code owner August 6, 2026 11:02
@copy-pr-bot

copy-pr-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

👋 Hi e-eygin! Thank you for contributing to ai-dynamo/nixl.

Your PR reviewers will review your contribution then trigger the CI to test your changes.

🚀

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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 change moves telemetry test utilities to nixl::metrics_test, adds shared multiprocess fixtures and scraping helpers, adds multiprocess core tests, and updates telemetry tests to use structured metrics and RAII resources.

Changes

Telemetry test consolidation

Layer / File(s) Summary
Shared metrics utilities
test/metrics/*
Shared socket, parser, time-series, and scraping helpers now use the metrics-test namespace. Scraping supports predicates and value matching. Socket ownership uses RAII.
Telemetry test migration
test/doca-telemetry/*, test/gtest/telemetry_histogram_test.cpp
Telemetry tests use shared metrics types, parsers, scraping helpers, and include paths.
Multiprocess fixtures and core coverage
test/gtest/mp_telemetry_fixture.h, test/gtest/telemetry_core_scrape.h, test/gtest/telemetry_mp_core_test.cpp, test/gtest/meson.build
Shared setup and overflow scraping support new allowlist, drop-accounting, and overflow-conservation tests.
Multiprocess test integration
test/gtest/telemetry_mp_*, test/gtest/telemetry_prometheus_test.cpp
Existing tests use shared fixtures, structured time-series assertions, and scoped descriptors.

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

Possibly related PRs

  • ai-dynamo/nixl#1920: Introduced multiprocess telemetry tests and fixtures that this change refactors and extends.

Suggested reviewers: ovidiusm

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: testing prometheus_mp through nixlTelemetry.
Description check ✅ Passed The description includes complete What, Why, and How sections with scope, design details, and verification results.
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
Contributor

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 `@test/gtest/telemetry_mp_e2e_test.cpp`:
- Around line 50-60: Update seriesByAgent to include the histogram bucket’s le
label when selecting or keying series, and adjust its callers/assertions to
validate the intended bucket (for example, le="+Inf"). Ensure distinct bucket
series no longer overwrite one another and that the test verifies bucket values
rather than only series presence.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: 6663a5fd-59c7-41de-ae5a-238830b122e3

📥 Commits

Reviewing files that changed from the base of the PR and between b56876d and ed2b735.

📒 Files selected for processing (19)
  • test/doca-telemetry/histogram_parity.h
  • test/doca-telemetry/meson.build
  • test/doca-telemetry/telemetry_doca_nixl_test.cpp
  • test/doca-telemetry/telemetry_doca_test.cpp
  • test/doca-telemetry/telemetry_histogram_parity_test.cpp
  • test/gtest/meson.build
  • test/gtest/mp_telemetry_fixture.h
  • test/gtest/telemetry_core_scrape.h
  • test/gtest/telemetry_histogram_test.cpp
  • test/gtest/telemetry_mp_collector_test.cpp
  • test/gtest/telemetry_mp_core_test.cpp
  • test/gtest/telemetry_mp_e2e_test.cpp
  • test/gtest/telemetry_mp_exporter_test.cpp
  • test/gtest/telemetry_mp_store_test.cpp
  • test/gtest/telemetry_prometheus_test.cpp
  • test/metrics/loopback_connection.h
  • test/metrics/open_metrics_text_parser.h
  • test/metrics/scrape_util.h
  • test/metrics/timeseries.h

Comment thread test/gtest/telemetry_mp_e2e_test.cpp

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/gtest/telemetry_mp_e2e_test.cpp (1)

135-137: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Acquire RAII ownership before the next fallible operation.

The scopedFd members take ownership only after all three pipe() calls succeed. If the second or third pipe() fails, earlier raw descriptors leak. If fork() fails, the raw read ends also remain open because the close block after the loop is skipped. Wrap both ends immediately after each successful pipe() or add an equivalent cleanup guard.

🤖 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 `@test/gtest/telemetry_mp_e2e_test.cpp` around lines 135 - 137, Update the pipe
setup in the telemetry test to transfer each successful pipe descriptor into
RAII ownership immediately after its pipe() call, before attempting subsequent
pipes or fork(). Ensure both read and write ends are owned or otherwise cleaned
up on any pipe or fork failure, including the read ends currently closed only
after the child-process loop.
🤖 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 `@test/gtest/telemetry_mp_e2e_test.cpp`:
- Around line 205-207: Update the zero-observation assertions in the telemetry
test to iterate over all entries in kChildren rather than checking only agent-0.
For each child writer, assert hist_count and hist_sum are 0.0 and the +Inf
bucket is optional<double>(0.0), reusing the existing bucket helper and
writer-name format.

---

Outside diff comments:
In `@test/gtest/telemetry_mp_e2e_test.cpp`:
- Around line 135-137: Update the pipe setup in the telemetry test to transfer
each successful pipe descriptor into RAII ownership immediately after its pipe()
call, before attempting subsequent pipes or fork(). Ensure both read and write
ends are owned or otherwise cleaned up on any pipe or fork failure, including
the read ends currently closed only after the child-process loop.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: d852d46a-f468-4c0b-898d-b6f78fa2484b

📥 Commits

Reviewing files that changed from the base of the PR and between ed2b735 and 55551fa.

📒 Files selected for processing (1)
  • test/gtest/telemetry_mp_e2e_test.cpp

Comment thread test/gtest/telemetry_mp_e2e_test.cpp Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/gtest/telemetry_mp_e2e_test.cpp (1)

135-137: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Own all pipe descriptors on every setup path.

If a fatal pipe() or fork() assertion exits before Lines 157-159, TearDown() resets only goWrite_, readyRead_, and quitWrite_. The other three descriptors remain raw and leak into later tests.

Create scoped owners for all six pipe ends immediately after each successful pipe(). Move or reset only the parent-owned ends after setup succeeds.

🤖 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 `@test/gtest/telemetry_mp_e2e_test.cpp` around lines 135 - 137, Update the pipe
setup in the test fixture so all six descriptors returned by each successful
pipe() are immediately wrapped in scopedFd owners. In the fork/setup flow around
the existing goWrite_, readyRead_, and quitWrite_ assignments, retain or reset
only the parent-owned ends after setup succeeds, ensuring TearDown() also owns
and closes descriptors when pipe() or fork() assertions abort early.
🤖 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.

Outside diff comments:
In `@test/gtest/telemetry_mp_e2e_test.cpp`:
- Around line 135-137: Update the pipe setup in the test fixture so all six
descriptors returned by each successful pipe() are immediately wrapped in
scopedFd owners. In the fork/setup flow around the existing goWrite_,
readyRead_, and quitWrite_ assignments, retain or reset only the parent-owned
ends after setup succeeds, ensuring TearDown() also owns and closes descriptors
when pipe() or fork() assertions abort early.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 4f0508bc-c02e-4289-881d-49cf5bf75873

📥 Commits

Reviewing files that changed from the base of the PR and between 55551fa and e66c1a0.

📒 Files selected for processing (1)
  • test/gtest/telemetry_mp_e2e_test.cpp

@e-eygin

e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the outside-diff finding from the last CodeRabbit review ("Own all pipe descriptors on every setup path") in 4c649dc.

It was valid. Only the three parent-retained ends were owned, and only after all three pipe() calls had succeeded, so a failing pipe() leaked the pipes opened before it and a failing fork() leaked the three ends handed to the children.

Each end is now wrapped in a scopedFd as soon as its pipe() returns. That also made the fixture's descriptor members unnecessary: locals close on every exit path, including a fatal assertion mid-fork, so TearDown() is left with just the child reaping.

MpE2ETest passes over --gtest_repeat=3, and the filtered suite is 88/88.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/gtest/telemetry_mp_e2e_test.cpp (1)

211-217: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Retain dead until it is reaped.

children_.erase(children_.begin()) runs before kill() and waitpid(). If either fatal assertion fails, GoogleTest leaves the test body and TearDown() no longer contains dead, so the child can remain unreaped. Move the erase after a successful waitpid().

Proposed cleanup ordering
 const pid_t dead = children_.front();
-children_.erase(children_.begin());
 const std::string dead_prefix =
     std::string(nixl::telemetry::mp::MP_STORE_FILE_PREFIX) + std::to_string(dead) + ".";
 ASSERT_EQ(countStores(dead_prefix), 1u);
 ASSERT_EQ(::kill(dead, SIGKILL), 0);
 ASSERT_EQ(::waitpid(dead, nullptr, 0), dead);
+children_.erase(children_.begin());
🤖 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 `@test/gtest/telemetry_mp_e2e_test.cpp` around lines 211 - 217, In the child
cleanup sequence using dead and children_, keep dead tracked until it has been
successfully terminated and reaped. Move children_.erase(children_.begin()) to
after the successful kill and waitpid assertions, preserving the existing
store-count validation and cleanup order.
🤖 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.

Outside diff comments:
In `@test/gtest/telemetry_mp_e2e_test.cpp`:
- Around line 211-217: In the child cleanup sequence using dead and children_,
keep dead tracked until it has been successfully terminated and reaped. Move
children_.erase(children_.begin()) to after the successful kill and waitpid
assertions, preserving the existing store-count validation and cleanup order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 0a165dc5-681e-47a0-8bb7-1e3519192e57

📥 Commits

Reviewing files that changed from the base of the PR and between e66c1a0 and 4c649dc.

📒 Files selected for processing (1)
  • test/gtest/telemetry_mp_e2e_test.cpp

@e-eygin

e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the outside-diff finding ("Retain dead until it is reaped") in 5fac215. Valid: the child was dropped from children_ before being killed, so a failing assertion in between left nobody responsible for reaping it.

Erasing only after waitpid() succeeds gets both halves right -- TearDown() finishes the job on an early exit, and the pid stops being tracked once reaped so a recycled one is never signalled.

MpE2ETest passes over --gtest_repeat=5; filtered suite 88/88.

@e-eygin

e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@ColinNV @ovidiusm please review

@e-eygin

e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

/build

@e-eygin
e-eygin requested a review from ColinNV August 6, 2026 14:42
@e-eygin

e-eygin commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 5fac215

The gtest Prometheus, histogram and multi-process tests each carried their
own HTTP client and hand-rolled exposition-format parsing, while the DOCA
tests already had a tested loopback client, OpenMetrics parser and
timeSeries query layer. Move those four headers to test/metrics/, rename
their namespace to nixl::metrics_test, and convert the gtest telemetry
tests to them, deleting the duplicated helpers.

scrape_util.h grows a generic scrapeUntil() poll-until-predicate that
scrapeUntilValue() and scrapeMetrics() are now expressed in terms of, and
telemetry_core_scrape.h adds scrapeCoreOverflow() for driving nixlTelemetry
with a small staging buffer and polling until accepted + dropped accounts
for every produced event. Replacing fixed sleeps with polling also cuts
suite runtime.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Everything prometheus_mp had constructed the exporter directly and called
exportEvent, which skips the core: the staging queue, the drain task,
per-metric gating and the drop accounting were never exercised for this
exporter. That gap blocks making it the configured exporter by default.

Lift the MpExporterTest fixture into mp_telemetry_fixture.h so a second
file can share it, and add telemetry_mp_core_test.cpp mirroring the
single-process coverage: the allowlist keeping a deactivated metric out
(asserted on the store as well as the scrape, which proves the event never
reached the exporter rather than merely not being rendered), the dropped
counter summing every delta, and conservation of accepted + dropped over a
flooded 256-slot queue for both the updateData and the all-or-none
addXferStats batch path.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
…ture

Four suites (store, collector, exporter, e2e) each opened their own
per-test temp directory and removed it again, two of them also repeating
the mode-0700 chmod the exporter needs to stay quiet, and the exporter
fixture extracted in the previous commit made that a fifth copy. Give them
a common mpTempDirTest base and derive MpExporterTest (directory plus port
and env) and MpE2ETest (plus child reaping) from it, so the 0700 rule and
the naming live in one place.

idx() had four identical copies and initParams() two; both move next to
the fixture.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
The e2e fixture kept three pipe ends as raw ints with its own closeFd(),
and loopbackConnection hand-rolled the same ownership in a constructor
error path, a destructor and findFreePort(). nixl::scopedFd already does
exactly this and is what the prometheus and collector tests use, so adopt
it: loopbackConnection loses its destructor, and the fixture's fds close
themselves.

Also mark the remaining loopbackConnection queries [[nodiscard]] -- the
rest of test/metrics already does -- and give the standalone doca_test
target the utils include path scoped_fd.h needs.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
…kets exist

seriesByAgent() keys on agent_name alone, so the bucket series of one agent --
which differ only by their le label -- collapsed onto a single entry. Counting
those entries proved every agent exposes some bucket series and nothing about
the distribution, so a sample in the wrong bucket passed.

Query the cumulative buckets by le instead: the parent's single 1234us sample
must leave le="1000" at zero and le="2500" and le="+Inf" at one, and a writer
that observed nothing must read zero at +Inf.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
The transfer-byte assertions already name each child, but the histogram ones
stopped at agent-0, so a wrong histogram for agent-1 or agent-2 passed. Loop
over the writers and assert count, sum and the +Inf bucket are all zero.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Only the three parent-retained ends were owned, and only once all three pipes
had been created: a failing pipe() leaked the pipes opened before it, and a
failing fork() leaked the three ends the parent hands to the children.

Wrap each end in a scopedFd as soon as its pipe() succeeds, so the fixture no
longer needs to hold any descriptor -- the locals close on every exit path,
including a fatal assertion mid-fork -- and TearDown is left with just the
child reaping.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
The child was dropped from children_ before it was killed, so a failing
assertion between the two left the test body with nobody responsible for
reaping it. Erase only after waitpid() succeeds: TearDown() finishes the job on
an early exit, and stops tracking the pid once reaped so a recycled one is
never signalled.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
The NIX-1205 stress tests (ai-dynamo#1951) landed while this branch was in review,
using the scrape helpers from their old home: namespace nixl::doca_test in
test/doca-telemetry/. This branch moves them to test/metrics/ as
nixl::metrics_test, so the two files no longer compile together.

Convert the two new stress sources, and drop the local scrapeMetrics copy in
favour of the shared one, which additionally retries an endpoint that is not
serving yet instead of parsing an empty body.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
@e-eygin
e-eygin force-pushed the nix-1682-prometheus-mp-core-tests branch from 5fac215 to 85a2ce1 Compare August 13, 2026 12:43
The stress suite (ai-dynamo#1951) landed its own copy of the overflow-conservation
harness while this branch was extracting the same logic into
telemetry_core_scrape.h. Keep one, taking its arguments as a spec struct so
the two knobs the stress case needs -- a 50 ms flush interval to keep drops
guaranteed under concurrent producers, and a sanitizer-scaled settle timeout
-- are named at the call site instead of being a second implementation.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
@e-eygin

e-eygin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

/build

@e-eygin

e-eygin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 7880fea

ovidiusm pushed a commit that referenced this pull request Aug 18, 2026
…2092)

## What?

`Tracing.RequestedBackendWithoutPluginIsInert` asked `makeTracer` for
the `nvtx` backend and expected a null tracer. It now asks for a backend
name no plugin can provide, matching what the sibling test
`Tracing.MakeTracerUnknownBackendReturnsNull` already did; the name
lives in one `kUnloadableBackend` constant used by both.

## Why?

The old assertion rested on a property of the *environment*, not of the
code: its own comment said "no `libtrace_backend_*.so` is registered in
this unit binary". Whenever a real `libtrace_backend_nvtx.so` is
discoverable, the plugin loads, `makeTracer` correctly returns a live
tracer, and the test fails. That is the case in the dev container, where
`NIXL_PLUGIN_DIR` points at an install tree containing the NVTX trace
plugin, and it would be the case in any CI leg that installs NIXL before
running the `unit` suite.

The test has been permanently red locally for weeks and was written off
as "pre-existing, unrelated" in the validation notes of three telemetry
PRs (#1952, #2054, #2086). Beyond the recurring explanation, a genuine
regression in the null-tracer / inert-`Span` path would have been
indistinguishable from the known-red state.

Coverage is unchanged: this test exists to prove that a null tracer
leaves call sites on the safe default-constructed `Span` path, which
never required NVTX specifically. Real NVTX behaviour stays covered by
the e2e `TestTransferTracing` tests, which load the actual plugin.

Tracking: NIX-1710.

<details>
<summary>Rejected alternative, and verification</summary>

Clearing `NIXL_PLUGIN_DIR` inside the test via the existing
`gtest::ScopedEnv` helper does not work reliably: `getPluginDir()` is
read once in the `nixlPluginManager` constructor and cached in
`plugin_dirs_` for the process lifetime, so the outcome would depend on
whether an earlier test in the binary already touched the plugin
manager.

Verified both directions with the container's default `NIXL_PLUGIN_DIR`,
i.e. with no workaround applied:

- Baseline (this change stashed, rebuilt): exactly one failure,
`RequestedBackendWithoutPluginIsInert`, reporting a live tracer pointer
where `nullptr` was expected.
- With the change: all 17 `Tracing.*` tests pass, and the full `unit`
suite is 157 passed / 2 skipped / 3 failed — the three failures being
`objCrtTestFixture.TransferBelowThreshold` and the two
`ObjClientTests/objParamTestFixture.ReadTransfer` params, which need an
object-storage endpoint this container does not provide.

</details>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Improved tracing plugin-loading tests to consistently cover
environments where the requested backend is unavailable.
* Updated inert-tracer coverage to validate behavior when no tracing
plugin can be loaded.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
@ovidiusm
ovidiusm merged commit e1cbabc into ai-dynamo:main Aug 18, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants