Skip to content

telemetry: native multi-process Prometheus aggregation (prometheus_mp)#1920

Open
e-eygin wants to merge 18 commits into
ai-dynamo:mainfrom
e-eygin:eeygin/nix-1614-native-mp-prometheus-aggregation
Open

telemetry: native multi-process Prometheus aggregation (prometheus_mp)#1920
e-eygin wants to merge 18 commits into
ai-dynamo:mainfrom
e-eygin:eeygin/nix-1614-native-mp-prometheus-aggregation

Conversation

@e-eygin

@e-eygin e-eygin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What?

Adds prometheus_mp, a new native telemetry exporter plug-in that exposes the telemetry of all processes of a multi-process NIXL run (tensor/data/pipeline parallelism) behind a single Prometheus scrape endpoint — with no external dependency (no DOCA/DTS, and not built on Python's prometheus_client).

Select it with NIXL_TELEMETRY_EXPORTER=prometheus_mp and point every rank at one shared directory via NIXL_TELEMETRY_MULTIPROC_DIR.

Components (each landed as its own reviewable commit):

  1. mp_store — a per-process, fixed-slot, memory-mapped metric-state store. Each process writes its own current counter/gauge values in place with lock-free __atomic ops. No serialization; the file is the state.
  2. mp_collector — a prometheus::Collectable that, on each scrape, globs the shared dir, reads a consistent snapshot of every live peer, and emits one series per (metric, process). Includes liveness/stale filtering and orphan reaping.
  3. prometheus_mp_exporter + plugin — bind-race owner election: every process writes its own store, the one that binds the scrape port becomes the owner (runs the Exposer + collector), the rest self-demote to writer-only. Built both as the installable plugin .so and as a static lib for unit testing.
  4. Tests — unit coverage for the store, the collector (family building, liveness, orphan reaping), and the exporter (owner / writer-on-collision / missing-dir), plus a forking multi-process e2e test that scrapes the owner over HTTP and asserts all ranks aggregate and that a killed rank is dropped and reaped.
  5. Docs — new src/plugins/telemetry/prometheus_mp/README.md and a multi-process section in docs/telemetry.md.

Why?

The existing native Prometheus exporter opens one HTTP scrape endpoint per process. Under multi-process parallelism every rank creates its own NIXL agent and tries to bind the same telemetry port; only one process can bind it, so the telemetry of every other rank is lost by design. This was reported for a Dynamo + SGLang TP=8 deployment on Kubernetes (GitHub issue #1838): one rank serves metrics and the rest log a bind failure and run without a sink.

The existing exporter can only make that failure benign (it logs a warning and the rank continues without telemetry) — it cannot recover the lost ranks. This PR makes every rank's telemetry available behind a single endpoint, natively.

Two deployment-shape constraints drove a native, dependency-free design rather than routing aggregation through an external service:

  • The affected users run the native Prometheus exporter specifically to avoid extra infrastructure, so requiring an external aggregator would defeat its purpose.
  • NIXL is a library loaded independently into each rank; there is no parent process to coordinate a leader, so ownership must be self-elected at runtime.

This complements (does not replace) the DOCA/CollectX exporter, which aggregates through an external telemetry service — that remains the right choice when such a service is already deployed; prometheus_mp is the standalone, native alternative.

Public tracking issue: #1838

How?

Architecture

One host/pod, shared NIXL_TELEMETRY_MULTIPROC_DIR

 rank A (bind winner)         rank B (writer)        rank N (writer)
   writes own mmap store        writes own store       writes own store
   Exposer :PORT  <-------- reads all live stores on each scrape --------+
     |
     | GET /metrics
  Prometheus

Every process writes its own store; exactly one — the bind-race owner — reads them all and serves. Losing the bind race is benign: the bind exception is caught internally and never propagated, so a writer is a fully valid telemetry sink and no rank is dropped.

Crash- and race-safety (the core of the design)

The store is a raw mmap of a fixed POD layout, so it must survive concurrent readers, new processes appearing mid-scrape, and processes killed mid-update:

  • Only the numeric value is written on the hot path, as a single 8-byte aligned __atomic op. SIGKILL cannot interrupt it, so readers never observe a torn value; a process killed mid-batch merely leaves a store a few increments behind (metrics are point-in-time snapshots, not a durable ledger).
  • Metric identity is fixed and positional. Names are never stored (the collector supplies them at scrape time); slots are indexed by event type. So there is no variable-length key/label written on the hot path — the exact class of write that otherwise forces per-process locking in a generic keyed store.
  • Per-process labels are written once at startup and committed by publishing a single atomic magic value last (release/acquire). A reader either sees a fully initialized header or skips the file; a process killed mid-creation leaves a zero-magic file that is skipped quietly and later reaped.

Series & labels

Per-process series (no cross-process summing, so counters stay monotonic), labeled hostname, agent_name, pid (guarantees cross-process uniqueness even if agent names collide; deliberately not the reserved instance label), agent_instance (distinguishes multiple same-name agents created in one process; 0 in the common single-agent case), and optional local_rank (the local/per-GPU (TP) rank, only when a rank env var is set). Metric names, types, and semantics are identical to the single-process prometheus exporter.

Cleanup & lifecycle

On clean shutdown a process removes its own store. A crashed or killed process cannot, so the owner reaps lazily during scrape: valid-but-dead stores via liveness (kill(pid,0) + /proc start time, which is PID-reuse safe) once their last update is past the TTL; unparseable orphans by file mtime past max(TTL, 2s) (the floor protects a store a live process is mid-creating). Whole-run cleanup is left to the deployment (e.g. a per-pod emptyDir), the same model container-based Prometheus multiprocess setups already use.

Configuration

Variable Description Default
NIXL_TELEMETRY_EXPORTER=prometheus_mp Select this exporter -
NIXL_TELEMETRY_MULTIPROC_DIR Shared local folder, same for all ranks (required) -
NIXL_TELEMETRY_PROMETHEUS_PORT / _LOCAL Scrape port / bind scope 9090 / public
NIXL_TELEMETRY_RANK_ENV Env var holding the rank for the optional local_rank label LOCAL_RANK
NIXL_TELEMETRY_MP_STALE_TTL Seconds after a dead process's last update before its store is reaped 30

NIXL_TELEMETRY_MULTIPROC_DIR must be a local filesystem (not NFS): mmap MAP_SHARED cross-process visibility depends on a local page cache. _STALE_TTL affects only already-dead processes (a live process is always published regardless); set it at least as large as your Prometheus scrape interval so a rank's final values are captured.

Scope / limitations (intentional)

Purpose-built for NIXL's fixed, low-cardinality, per-process label model — it cannot represent a metric with a dynamic/high-cardinality label that varies per observation (none exist today; adding one would require a different, keyed store). Proactive owner failover (a writer promoting itself if the owner dies) is out of scope: until a new agent re-binds the freed port, writers keep writing but nothing serves.

Testing

Runs under CTest/meson: store, collector, and exporter unit tests plus the forking e2e. All prometheus_mp tests pass; changed lines are clang-format-19 clean and every commit is DCO-signed.

Summary by CodeRabbit

  • New Features
    • Added an experimental Prometheus multiprocess telemetry plugin (prometheus_mp) that aggregates metrics into a single /metrics endpoint with scrape owner election.
    • Introduced an mmap-backed per-process metric state store with configurable stale TTL and optional rank labeling.
  • Documentation
    • Added documentation for multiprocess aggregation behavior, configuration variables, label model, and stated limitations (fixed metric set; no dynamic/high-cardinality labels) plus guidance for external aggregation.
  • Tests
    • Added unit and end-to-end coverage for the multiprocess store, collector aggregation, exporter owner/writer modes, and stale reaping/scrape lifecycle.

e-eygin added 9 commits July 9, 2026 18:39
First component of the native multi-process Prometheus aggregation
(prometheus_mp) plugin. mp_store is a per-process, fixed-slot memory-mapped
store holding current metric state (cumulative counters and last-operation
gauges) indexed by nixl_telemetry_event_type_t, for scrape-time aggregation
by a single exporter process.

It is a raw mmap of a fixed POD layout (no serialization): writers update
slots in place with lock-free __atomic ops; a reader validates the
magic/schema/size header and returns a consistent snapshot. Per-process
identity (pid + /proc start_time, agent name, hostname, optional dp_rank)
lives in the header for liveness and labeling. Built as a small
prometheus-cpp-free static library with CTest coverage.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Second component of the prometheus_mp plugin. nixlMultiprocessCollector is a
prometheus::Collectable that, on each scrape, globs the shared telemetry
directory for peer store files, reads a snapshot of each, drops (and optionally
reaps) stale ones, and returns per-process metric families.

Series are emitted per (metric, process) with no cross-process aggregation:
cumulative counters and last-operation gauges keyed by
nixl_telemetry_event_type_t, plus agent_errors_total with a status label,
labeled by hostname, agent_name and (when present) dp_rank. Staleness combines
pid liveness (kill(pid,0) + /proc start_time to guard PID reuse) with a
last-update TTL. The family-building and liveness logic are split into pure
functions for unit testing; a shared store file-naming helper is added to
mp_store. Collector library and tests are gated on prometheus-cpp.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
The collector's per-process series were disambiguated only by agent_name
(plus optional dp_rank). If two processes share an agent name and have no
dp_rank, they would emit an identical label set -> a duplicate Prometheus
series and a rejected scrape.

Add an unconditional pid label so every live process is a distinct series
regardless of how callers name agents. pid (not the reserved "instance"
target label) also avoids the exported_instance renaming trap.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Third component of the prometheus_mp plugin: the exporter that ties the store
and collector together, selectable via NIXL_TELEMETRY_EXPORTER=prometheus_mp.

Every process writes its own metric state to a per-process store in
NIXL_TELEMETRY_MULTIPROC_DIR (unique file per pid/instance). On construction
each process races to bind the scrape port: the winner runs a prometheus-cpp
Exposer plus a nixlMultiprocessCollector that aggregates all peers on each
scrape; losers fall back to writer-only mode. A bind collision is caught
internally and never rethrown as nixlTelemetryBindFailed, so every process --
owner or writer -- gets a valid telemetry sink and all ranks are exported
behind the single owner endpoint.

Config: NIXL_TELEMETRY_MULTIPROC_DIR (required), NIXL_TELEMETRY_RANK_ENV
(optional dp_rank source, default LOCAL_RANK), NIXL_TELEMETRY_MP_STALE_TTL;
reuses NIXL_TELEMETRY_PROMETHEUS_PORT / _LOCAL. The exporter is built both as
the installable plugin .so and as a static library for direct unit testing
(owner mode, writer-mode-on-collision, missing-dir). Gated on prometheus-cpp.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Fourth component of the prometheus_mp plugin: an end-to-end test that proves
cross-process aggregation with real processes. The parent becomes the
bind-race owner and serves the endpoint; N children fork (while the parent is
still single-threaded) and run as writers against the shared dir. After a
single HTTP scrape of the owner port, all N+1 processes appear as distinct
per-process series. One child is then killed and reaped, and a second scrape
confirms its series is dropped and its store file removed (stale TTL 0).

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Handle processes that appear or die mid-scrape more robustly:

- Reader treats a zero-magic store (a process still initializing its file, or
  an orphan from a process killed mid-creation) as a quiet skip instead of a
  "bad magic" WARN; a genuinely wrong non-zero magic still warns.
- Collector now reaps unparseable store files (zero/bad magic, wrong schema,
  truncated) once they are older than max(stale TTL, 2s). The floor protects a
  store a live process is actively creating from being deleted out from under
  it, even when the TTL is 0.

This closes a slow file leak: a process killed during store creation left a
zero-magic file that was correctly skipped every scrape but never cleaned up.
Cleanup is performed by the bind-race owner during Collect(), since a killed
process cannot clean up after itself.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Describe the native single-endpoint multi-process aggregation exporter in
docs/telemetry.md: add it to the architecture components, update the
multi-process scrape note to point ranks needing full aggregation at it, and
add a "Multi-process aggregation" section covering its model, configuration
(NIXL_TELEMETRY_MULTIPROC_DIR/RANK_ENV/MP_STALE_TTL), labels, and the explicit
limitation that it is hardcoded to NIXL's fixed metric model and cannot carry
dynamic per-observation labels.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Document the multi-process Prometheus exporter plug-in alongside the sibling
prometheus and doca plugin READMEs: how bind-race owner election and the
per-process mmap stores work, configuration (NIXL_TELEMETRY_MULTIPROC_DIR and
the optional RANK_ENV / MP_STALE_TTL / port vars), the per-process labels
(hostname, agent_name, pid, optional dp_rank), and the explicit scope note that
it is purpose-built for NIXL's fixed metric model (no dynamic per-observation
labels) rather than a generic Prometheus multiprocess store.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Clarify that NIXL_TELEMETRY_MULTIPROC_DIR follows Dynamo's
PROMETHEUS_MULTIPROC_DIR convention: a shared local folder (not NFS), one per
pod/process-family, treated as ephemeral. Note the key difference from Dynamo:
NIXL is loaded independently per rank with no parent to propagate the path, so
the launcher/operator must set the same directory for every rank (hence it is
required, not auto-defaulted). tmpfs is optional, not required.

Part of NIX-1614.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
@e-eygin e-eygin self-assigned this Jul 9, 2026
@e-eygin e-eygin requested a review from a team as a code owner July 9, 2026 20:05
@copy-pr-bot

copy-pr-bot Bot commented Jul 9, 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 Jul 9, 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 Jul 9, 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

Adds an experimental prometheus_mp telemetry plugin that writes per-process metrics to mmap-backed store files, aggregates them at scrape time, and exposes one Prometheus endpoint. It also adds owner election, build wiring, tests, and telemetry documentation.

Changes

Prometheus MP exporter

Layer / File(s) Summary
Per-process mmap metric store
src/plugins/telemetry/prometheus_mp/mp_store.{h,cpp}, test/gtest/telemetry_mp_store_test.cpp
Defines the mmap store layout, process identity, atomic metric updates, snapshot validation, and cleanup tests.
Scrape-time multiprocess collector
src/plugins/telemetry/prometheus_mp/mp_collector.{h,cpp}, test/gtest/telemetry_mp_collector_test.cpp
Adds liveness and TTL filtering, per-process Prometheus metric families, directory scanning, and stale-file reaping.
Owner-elected exporter and plugin entrypoints
src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.{h,cpp}, prometheus_mp_plugin.cpp, test/gtest/telemetry_mp_exporter_test.cpp
Adds environment configuration, event recording, scrape-port ownership selection, serving, and plugin lifecycle hooks.
End-to-end multiprocess aggregation test
test/gtest/telemetry_mp_e2e_test.cpp
Tests forked writers behind one scrape endpoint and removal of a terminated process series.
Build wiring and documentation
src/plugins/telemetry/meson.build, src/plugins/telemetry/prometheus_mp/meson.build, test/gtest/meson.build, src/plugins/telemetry/prometheus_mp/README.md, docs/telemetry.md
Adds plugin and test build targets and documents configuration, labels, aggregation, and limitations.

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

Possibly related PRs

Suggested reviewers: ColinNV, iyastreb, ovidiusm, guy-ealey-morag, AlexNvd

Sequence Diagram(s)

sequenceDiagram
  participant Process
  participant nixlTelemetryPrometheusMpExporter
  participant mpStoreWriter
  participant PrometheusExposer
  participant nixlMultiprocessCollector

  Process->>nixlTelemetryPrometheusMpExporter: construct exporter
  nixlTelemetryPrometheusMpExporter->>mpStoreWriter: create per-process mmap store
  nixlTelemetryPrometheusMpExporter->>PrometheusExposer: attempt port bind
  PrometheusExposer-->>nixlTelemetryPrometheusMpExporter: owner or writer-only mode
  Process->>nixlTelemetryPrometheusMpExporter: exportEvent
  nixlTelemetryPrometheusMpExporter->>mpStoreWriter: write metric update
  PrometheusExposer->>nixlMultiprocessCollector: scrape /metrics
  nixlMultiprocessCollector->>mpStoreWriter: read live snapshots
  nixlMultiprocessCollector-->>PrometheusExposer: return MetricFamily list
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.13% 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
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 clearly summarizes the new native multi-process Prometheus exporter.
Description check ✅ Passed The PR description includes the required What, Why, and How sections with detailed design, motivation, and testing notes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

CI pre-commit (codespell) flagged the local nowNs() helper (read as
"knowns/nouns") and "unparseable".

- Replace the duplicated per-file nowNs() helpers with the existing
  nixlTime::getNs() (CLOCK_MONOTONIC, host-wide so comparable across
  processes; also skew-free vs wall clock for the staleness delta). The
  last-update timestamp is only ever compared against another getNs() reading,
  so switching from system_clock to steady_clock is safe; the orphan-file mtime
  path is independent and still uses wall-clock time().
- Spell "unparseable" as "unparsable" in comments.

Part of NIX-1614.

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

e-eygin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai help

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai emit path instructions to emit generated path-specific review instructions for this repository.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@e-eygin

e-eygin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@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: 5

🤖 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 `@src/plugins/telemetry/prometheus_mp/mp_collector.cpp`:
- Around line 138-191: The reads in buildMetricFamilies are indexing fixed-size
snapshot arrays directly from telemetry_metric_event_types and
telemetry_error_event_types, so add bounds protection before accessing
snap.counters or snap.gauges. Update the buildMetricFamilies logic to use a
checked access path (such as .at() or an explicit range guard) for the slot
derived from the enum, and make sure the error-family loop uses the same
protection. Keep the existing telemetryMetricDescriptor, counterMetric, and
gaugeMetric flow unchanged otherwise.
- Around line 200-235: The Collect() scan still relies on the range-based for
over std::filesystem::directory_iterator, so an implicit iterator increment can
throw even though the constructor uses std::error_code. Update
nixlMultiprocessCollector::Collect() to handle iterator advancement errors
explicitly, either by switching to a manual directory_iterator loop that uses
the non-throwing increment overload with an error_code or by wrapping the
iteration in a try/catch that safely returns an empty result on
filesystem_error. Keep the existing dir_ scan, readStoreSnapshot, and reapStale_
logic intact while making the traversal resilient to mid-scan directory changes.

In `@src/plugins/telemetry/prometheus_mp/mp_store.cpp`:
- Around line 22-32: mp_store.cpp is using std::min and std::istream_iterator
without directly including their standard headers. Update the file’s include
list near the top to add both <algorithm> and <iterator> so MpStore’s
implementation does not rely on transitive includes.

In `@src/plugins/telemetry/prometheus_mp/mp_store.h`:
- Around line 41-47: The slot-count derivation in MP_STORE_SLOT_COUNT is fragile
because it assumes AGENT_TELEMETRY_EVENTS_DROPPED remains the last enum value.
Add a compile-time safeguard in mp_store.h, near the MP_STORE_SLOT_COUNT
definition, using a static_assert that validates the telemetry event enums fit
within the array bounds. Make the check cover the relevant
nixl_telemetry_event_type_t values used by mp_collector.cpp’s
buildMetricFamilies so future enum extensions fail at compile time instead of
causing out-of-bounds access.

In `@src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp`:
- Around line 40-49: The telemetry constant declarations in
prometheus_mp_exporter.cpp use constexpr char[] while the matching constants in
mp_store.h use inline constexpr std::string_view, so make them consistent.
Update the identifiers such as defaultRankEnvName, prometheusPortVar,
prometheusLocalVar, multiprocDirVar, rankEnvVar, and staleTtlVar to use the same
string_view style as the sibling component, keeping the existing names and
usages intact.
🪄 Autofix (Beta)

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: 249ccc98-927b-47c3-95a4-ea65420aac52

📥 Commits

Reviewing files that changed from the base of the PR and between 424a60c and b4093af.

📒 Files selected for processing (16)
  • docs/telemetry.md
  • src/plugins/telemetry/meson.build
  • src/plugins/telemetry/prometheus_mp/README.md
  • src/plugins/telemetry/prometheus_mp/meson.build
  • src/plugins/telemetry/prometheus_mp/mp_collector.cpp
  • src/plugins/telemetry/prometheus_mp/mp_collector.h
  • src/plugins/telemetry/prometheus_mp/mp_store.cpp
  • src/plugins/telemetry/prometheus_mp/mp_store.h
  • src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp
  • src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.h
  • src/plugins/telemetry/prometheus_mp/prometheus_mp_plugin.cpp
  • test/gtest/meson.build
  • test/gtest/telemetry_mp_collector_test.cpp
  • test/gtest/telemetry_mp_e2e_test.cpp
  • test/gtest/telemetry_mp_exporter_test.cpp
  • test/gtest/telemetry_mp_store_test.cpp

Comment thread src/plugins/telemetry/prometheus_mp/mp_collector.cpp
Comment thread src/plugins/telemetry/prometheus_mp/mp_collector.cpp
Comment thread src/plugins/telemetry/prometheus_mp/mp_store.cpp
Comment thread src/plugins/telemetry/prometheus_mp/mp_store.h
Comment thread src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

… includes)

- mp_store.h: add a compile-time static_assert that MP_STORE_SLOT_COUNT covers
  every telemetry event type the collector indexes, so extending the enum past
  AGENT_TELEMETRY_EVENTS_DROPPED fails the build instead of causing an
  out-of-bounds counter/gauge access.
- mp_collector.cpp: iterate the shared directory with the non-throwing
  increment(ec) instead of the range-for's throwing operator++, since peer
  writers and this collector's own reaping mutate the directory concurrently
  and a mid-iteration filesystem error would otherwise escape Collect().
- mp_store.cpp: add explicit <algorithm> and <iterator> includes (std::min,
  std::istream[buf]_iterator) rather than relying on transitive includes.

Part of NIX-1614.

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

e-eygin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/build

@e-eygin

e-eygin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 3bf2139

The optional per-process rank label was named dp_rank, but its value is sourced
from the local/per-GPU (tensor-parallel) rank env (default LOCAL_RANK), not the
data-parallel rank. That misdescribed the value and collided with Dynamo's own
data-parallel dp_rank series.

Rename the emitted label and the internal identifiers (store field, writer
param, exporter helper) to local_rank. The NIXL_TELEMETRY_RANK_ENV var and its
LOCAL_RANK default are unchanged; the label is still optional and emitted only
when the env is set. Docs and tests updated.

Part of NIX-1614.

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

e-eygin commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@ColinNV @ovidiusm please review

e-eygin added 2 commits July 10, 2026 15:31
The prometheus_mp store writer only unmapped its file on destruction, so an
agent destroyed while its process keeps running left a store behind. Because
liveness is pid-based, the collector kept publishing that dead agent's frozen
series indefinitely.

Have ~mpStoreWriter() best-effort remove its own file: clean shutdown is the
deterministic "producer gone" signal, while crash/kill (destructor never runs)
still falls back to the owner's liveness/TTL reaping. Adjust the store
round-trip tests to read while the writer is alive (the real cross-process
pattern) and add a test for the new cleanup.

Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
Multiple agents created in one process with the same agent name shared every
series label (hostname, agent_name, pid, local_rank), producing duplicate
Prometheus series that make the whole scrape fail. The store filename already
carried a per-process instance counter, but it never reached the exposed labels.

Persist the instance in the store header and emit it as an agent_instance label,
so same-name same-process agents get distinct series (agent_instance=0 for the
common single-agent case). Schema version is unchanged: prometheus_mp has not
shipped, so no on-disk format compatibility is at stake.

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

NIXL_TELEMETRY_MP_STALE_TTL is measured from a dead process's last update, not
from the moment of death; a live process is always published regardless of age.
Reword both docs to say so. Also add the agent_instance label to the telemetry.md
label list to match the prometheus_mp exporter.

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

@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)
src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp (1)

105-146: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Document the "construct after fork" invariant.

mpStoreWriter construction ties the store file to the current process's pid (getpid()) and a per-process instance counter. If a caller ever constructs this exporter before fork()ing (rather than once per process, post-fork), parent and child would share the same store file/pid label, and whichever writer is destructed first (mp_store.cpp's destructor now removes the file) would silently delete metrics visibility for the other, still-live process. This appears to be relied upon by the forking end-to-end test described in the PR. Consider adding a short comment here (or in the class doc) stating this construction-after-fork requirement explicitly.

🤖 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 `@src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp` around lines
105 - 146, Add a concise comment near nixlTelemetryPrometheusMpExporter
construction, or in its class documentation, explicitly stating that each
exporter must be constructed once per process after fork. Mention that
mpStoreWriter binds its store file to the current PID and per-process instance,
so pre-fork construction can cause parent and child to share a file and
interfere during destruction.
🤖 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 `@src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp`:
- Around line 105-146: Add a concise comment near
nixlTelemetryPrometheusMpExporter construction, or in its class documentation,
explicitly stating that each exporter must be constructed once per process after
fork. Mention that mpStoreWriter binds its store file to the current PID and
per-process instance, so pre-fork construction can cause parent and child to
share a file and interfere during destruction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 23c90a9c-c01f-4b2b-bba8-e0915fd5e09b

📥 Commits

Reviewing files that changed from the base of the PR and between cd22887 and f5de93f.

📒 Files selected for processing (7)
  • src/plugins/telemetry/prometheus_mp/README.md
  • src/plugins/telemetry/prometheus_mp/mp_collector.cpp
  • src/plugins/telemetry/prometheus_mp/mp_store.cpp
  • src/plugins/telemetry/prometheus_mp/mp_store.h
  • src/plugins/telemetry/prometheus_mp/prometheus_mp_exporter.cpp
  • test/gtest/telemetry_mp_collector_test.cpp
  • test/gtest/telemetry_mp_store_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

🤖 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 `@docs/telemetry.md`:
- Around line 160-164: Update the telemetry documentation near the series-label
description to explicitly document the `status` label on every
`agent_errors_total` series, matching
`src/plugins/telemetry/prometheus_mp/README.md` and the collector behavior while
distinguishing it from the common labels.
🪄 Autofix (Beta)

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: 83929b5c-a234-497f-aecb-eb2160e08d6e

📥 Commits

Reviewing files that changed from the base of the PR and between f5de93f and 4c74834.

📒 Files selected for processing (2)
  • docs/telemetry.md
  • src/plugins/telemetry/prometheus_mp/README.md

Comment thread docs/telemetry.md
The prometheus_mp label list omitted that agent_errors_total additionally carries
the bounded status label, unlike the plugin README. Align the two.

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

e-eygin commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

/build

@e-eygin

e-eygin commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test ad437a8

e-eygin added 2 commits July 14, 2026 12:31
…tion

Resolve test/gtest/meson.build: keep both the prometheus_mp store test and the
newly-merged transfer-duration histogram test in gtest_sources. docs/telemetry.md
auto-merged (histogram and multiprocess sections are independent).

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

Resolve docs/telemetry.md: keep both new exporter list entries (prometheus_mp
and the DOCA IPC->DTS description from ai-dynamo#1897), renumbered. test/gtest/meson.build
auto-merged (ai-dynamo#1919 activation test alongside the mp/histogram tests).

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

e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

/build

@e-eygin

e-eygin commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test aa3f6bd

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 9f7abf6f

TL;DR: The elastic EP fault-tolerance test (expansion_fault_contraction.json) crashes with a CUDA "illegal memory access" only on the UCX-master build; the same commit passes on UCX v1.22.x, so this is a UCX-master-specific regression in the peer-death / UCP-endpoint-close path, not a NIXL Python-test bug. Fix: guard/serialize the forced UCP endpoint teardown so it doesn't touch already-freed CUDA resources when a peer dies mid-combine.

Full analysis

Summary: Stage 203 "Run DL EP elastic tests" (UCX master variant) failed — when rank 2 was intentionally killed mid-dispatch/combine, the surviving ranks hit a CUDA illegal-memory-access and exited with code 1 instead of gracefully detecting the failure.

Root cause: UCX-master-specific regression in the endpoint teardown path. In the failing (master) run, immediately after [rank 2] Killing rank during dispatch/combine, the surviving ranks produce CUDA warning: an illegal memory access was encountered (function destroyEvent) and UCX cuMemFree_v2((CUdeviceptr)self->device_ep) failed / cuStreamDestroy_v2(*stream) failed (cuda_ipc_ep.c:51, cuda_iface.c:371), then crash. The passing (v1.22.x) run of the identical test and commit instead cleanly hits Warning: NIXL-EP timeout for combine receive ... src_rank 2 followed by detected rank failures: {2}, cleaning up... and completes all phases. The only difference between the two runs is the UCX version, so the illegal memory access comes from the UCX forced-endpoint-close / CUDA IPC teardown when a peer dies mid-combine — most likely introduced by the recently added forced UCP endpoint close option.

Implicated commit: 22cdab3de2e9 — "PLUGINS/UCX: Added option to forcibly close UCP endpoints. (#1846)", Raul Akhmetshin, 2026-07-15 (interacting with the UCX-master CUDA IPC teardown in cuda_ipc_ep.c / cuda_iface.c). Note: this failure is in the UCX-master pipeline variant, so it is likely not caused by PR #1920's own changes.

File: examples/device/ep/tests/elastic/elastic.py:118-124 (self_kill during combine) is where the fault is injected; the actual crash originates in the UCX CUDA IPC endpoint teardown (UCX cuda_ipc_ep.c:51, cuda_iface.c:371) reached via NIXL's forced UCP endpoint close (src/plugins/ucx, commit #1846).

Suggested fix: In the forced UCP endpoint close path added in #1846, ensure endpoint/CUDA-IPC teardown does not free or destroy CUDA resources (device_ep / streams / events) that a dead peer may still reference — e.g. gate the forced close behind the existing combine-timeout fault-detection so the graceful "detected rank failures / cleaning up" path (which works on v1.22.x) runs instead of an immediate teardown, or set the correct CUDA device/context before cuMemFree_v2/cuStreamDestroy_v2. Bisect against UCX master to confirm #1846 is the trigger; if the change is UCX-side rather than NIXL-side, pin/patch the UCX-master build used by the build_helper_dl_ep/aarch64/ucx-master variant. Do not treat this as a test bug — the Python test is correct and passes on stable UCX.

Related: PR #1846 (#1846); this failure is on PR #1920's CI run but appears to be a UCX-master regression independent of #1920.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 9f7abf6f

TL;DR: The "Run Nixlbench tests" stage (ID 356) was killed (exit 143/SIGTERM) after hanging for ~18 minutes in the UCCL backend; the hang — not a time limit — is the root cause, and it is unrelated to PR #1920 (a telemetry-only change), so this is a flaky/pre-existing UCCL teardown/connection deadlock.

Full analysis

Summary: nixl-ci-gpu #2775 stage "Run Nixlbench tests" was aborted after a nixlbench UCCL two-worker test hung during connection accept/engine-destroy and never progressed to the next test iteration.

Root cause: A hang, not a slow test. The last application output was at 06:21:51.511Z (UCCL READ VRAM→DRAM over ETCD: "Engine destroyed" / "Error/HUP on connection: 1.1.101.3:32813"). The next line is 06:39:37.228Z "Sending interrupt signal to process" — a ~18-minute dead gap with no output, followed by SIGTERM (script returned exit code 143) and the build agent going offline. The process hung while spinning up the next iteration (UCCL READ DRAM→VRAM). During the preceding UCCL teardown the two workers did not shut down symmetrically (one side kept printing "Waiting to accept incoming connection..." while only one reached "Engine destroyed"), pointing to a UCCL backend accept/engine-destroy deadlock, consistent with the open "UCCL crash Fix" work in PR #1724. The triggering PR #1920 only touches telemetry (prometheus_mp) and does not touch nixlbench or the UCCL plugin, so it did not cause this.

Implicated commit: unknown (not introduced by PR #1920 / commit 9f7abf6). Hang is in the UCCL backend; last-touched by Pravein Govindan Kannan in src/plugins/uccl (e.g. f8e2221 "Enable local xfer for UCCL backend", 63c494e "UCCL: Simplify and Optimize for batch transfers").

File: benchmark/nixlbench UCCL two-worker test loop + src/plugins/uccl/uccl_backend.cpp (connection accept / engine teardown path). Exact hang line not resolvable from logs alone.

Suggested fix: Do not raise the stage time limit — that would only mask the hang. Instead: (1) re-run to confirm this is the known flaky UCCL hang (PR #1920 is telemetry-only and shouldn't gate on it); (2) add a per-test timeout wrapper around each nixlbench invocation in the nixlbench CI script so a single UCCL hang fails fast with a stack dump instead of killing the whole 26-min stage; (3) investigate the UCCL backend's accept/~Engine shutdown ordering for the connection-teardown deadlock, coordinating with PR #1724 ("UCCL crash Fix + GDB trap").

Related: PR #1724 (UCCL crash Fix + GDB trap); triggering PR #1920 (telemetry prometheus_mp, unrelated).

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 9f7abf6f

TL;DR: Both "Compiling NIXL EP Docker Image for DL" stages failed because apt-get install gdb gdb-multiarch couldn't reach ports.ubuntu.com ("Network is unreachable" / connection timed out on every mirror IP) — an infrastructure/network outage, not a code bug. Retry the build once the build node's network/mirror access is restored.

Full analysis

Summary: The Docker image build (stages 150 and 168) failed at the RUN sudo apt-get update && sudo apt-get install -y ... gdb gdb-multiarch && .gitlab/build.sh ... step in Dockerfile.gpu-test, exiting with code 100.

Root cause: apt-get could not download packages from http://ports.ubuntu.com — every attempt returned "Cannot initiate the connection to ports.ubuntu.com:80 ... connect (101: Network is unreachable)" and "connection timed out" against all mirror IPs (IPv6 2620:2d:4002:1::10a/b/c and IPv4 91.189.91.102/103/104). apt-get update had already warned "Some index files failed to download" earlier in the step. This is a transient network/DNS/egress problem on the aarch64 build node (or an Ubuntu mirror outage), affecting both the ucx-master and ucx-v1.22.x parallel builds identically. It is unrelated to the code in PR #1920.

Implicated commit: None — not a code regression. (The apt step itself was last touched by [REDACTED:Hex High Entropy String], NirWolfer, "ci: export container on test failure for crash debugging (#1800)", but that commit is not the cause; the failure is network egress.)

File: .ci/dockerfiles/Dockerfile.gpu-test:34-38 (the failing RUN apt-get layer)

Suggested fix:

  • Re-run the build; if the node's network is healthy the apt fetch will succeed.
  • If this recurs, it's an infra issue: verify the aarch64 build agent's outbound network/IPv6 egress and DNS, and prefer routing apt through the internal mirror/proxy (e.g. an Artifactory apt remote) instead of the public ports.ubuntu.com, since the build already pulls base images from artifactory.nvidia.com.
  • Hardening option: point /etc/apt/sources.list at the internal mirror and/or add --fix-missing/retry logic so transient mirror flakiness doesn't fail the whole pipeline.

Related: none

Note: the podman build --creds svc-nixl:**** credential in the log is already masked by Jenkins; no action needed there.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 7f7c335b-7b14-4a8a-96cc-c921f1f98886 in the triage console for the audit trail.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-dl-gpu-ep · commit 9f7abf6f

TL;DR: The "Run DL EP elastic tests" stage failed because the expansion_fault_contraction fault-injection test crashed the surviving ranks (workers 0, 2, 3 exited code 1) with a CUDA "illegal memory access" during UCX endpoint teardown when peer rank 2 was intentionally killed. The regression points at PR #1846's forcibly-close-UCP-endpoints path; fix the endpoint teardown so peer death during dispatch/combine is handled gracefully instead of dereferencing freed CUDA IPC memory.

Full analysis

Summary: Elastic EP test expansion_fault_contraction.json (phase 2, rank-kill scenario) caused non-killed ranks to abort with CUDA illegal memory access instead of surviving the peer failure.

Root cause: In the fault-injection phase, rank 2 receives SIGTERM as planned ([0, 1, -2, 3]). The remaining ranks should keep running, but instead crashed: CUDA warning: an illegal memory access was encountered (destroyEvent) followed by UCX errors cuMemFree_v2((CUdeviceptr)self->device_ep) failed and cuStreamDestroy_v2 failed in cuda_ipc_ep.c:51 / cuda_iface.c:371. The UCX CUDA-IPC endpoint teardown dereferences/free's device memory associated with the dead peer's endpoint, triggering the illegal access, so workers 0/2/3 exit with code 1 (not the expected -SIGTERM) and elastic.py:692 raises RuntimeError: Worker processes failed. This is the exact code path introduced by the most recent commit. Note: the earlier "Compiling NIXL EP Docker Image (master)" failure in stage 150 is an unrelated transient apt network outage (ports.ubuntu.com ... Network is unreachable) that succeeded on retry in stage 168 — not the gating failure.

Implicated commit: 22cdab3 — "PLUGINS/UCX: Added option to forcibly close UCP endpoints. (#1846)", Raul Akhmetshin, 2026-07-15 (most recent change, directly touches UCP endpoint close/teardown behavior exercised by this fault-tolerance test).

File: UCX plugin endpoint-close path (introduced by #1846) — e.g. src/plugins/ucx/ endpoint teardown; failure surfaces in UCX cuda_ipc_ep.c:51 and is asserted by examples/device/ep/tests/elastic/elastic.py:692.

Suggested fix: In the forcibly-close-UCP-endpoints path added by #1846, guard the CUDA-IPC endpoint teardown so it does not call cuMemFree/cuStreamDestroy on device resources belonging to an already-dead peer (e.g. tolerate/clear CUDA errors during forced close, skip freeing peer IPC memory when the remote is gone, or use a "force close without device cleanup" mode). Reproduce locally with examples/device/ep/tests/elastic/elastic.py --plan .../expansion_fault_contraction.json --validate-phase-failures. If #1846 is confirmed as the cause, consider reverting or gating the forced-close behavior behind the new option being off by default until the teardown is made peer-death-safe.

Related: PR #1846 (#1846); possibly related test-hardening PR #1909.

@svc-nixl

Copy link
Copy Markdown
Collaborator

🤖 CI Triage Agentnixl-ci-gpu · commit 9f7abf6f

TL;DR: The "Run Nixlbench tests" stage was ABORTED (exit 143) not because it was slow, but because the UCCL backend hung during the startup of the UCCL WRITE DRAM→VRAM two-worker test — the node stopped producing output at 07:43:19 and went unreachable, and Jenkins killed it ~23 minutes later. Investigate the UCCL engine connection/teardown path, not the CI time limit.

Full analysis

Summary: Jenkins stage Run Nixlbench tests (v1.22.x, node 318) was aborted after a ~23-minute hang; the last successful test was UCCL WRITE VRAM→DRAM and the following UCCL WRITE DRAM→VRAM test never initialized.

Root cause: A hang in the UCCL backend during two-worker (ASIO) benchmark setup. The final application log line is at 07:43:19.401Z ("Engine destroyed" / Error/HUP on connection: 1.1.101.1:34721) after the UCCL WRITE VRAM→DRAM case. The next loop iteration (UCCL WRITE DRAM→VRAM, .gitlab/test_nixlbench.sh:99) never printed its "UCCL backend / Creating Engine" banner. At 07:43:28.399Z the build node became unreachable (ClosedChannelException), and there is then a dead gap until 08:06:00 when Jenkins sent SIGTERM (exit code 143). Continuous silence for the majority of the stage's runtime = hung process, not a genuinely slow test. UCCL runs were already visibly degraded (0.03 GB/s throughput, ~450µs latencies, repeated pin_thread_to_numa failures and Error/HUP on connection warnings), consistent with a UCCL RDMA connection/engine-lifecycle stall or deadlock.

Implicated commit: Not conclusively one commit from the log alone. Most relevant suspects are the UCCL backend changes: 63c494e1 (Pravein Govindan Kannan — "UCCL: Simplify and Optimize for batch transfers") and f8e2221f ("Enable local xfer for UCCL backend"). The failing PR is #1920 (commit 9f7abf6) — the diff of that PR should be checked against the UCCL path.

File: .gitlab/test_nixlbench.sh:99 (the UCCL two-worker loop that hung); root cause lives in the UCCL plugin engine setup/teardown under src/plugins/uccl (connection accept / Destroying Engine / epoll event_loop path referenced by rdma/epoll_client.h:207).

Suggested fix: Do not raise the Jenkins/SLURM time limit — that treats the symptom. Instead: (1) add a per-invocation timeout wrapper around each run_nixlbench_two_workers_asio call (e.g. timeout 120 parallel ...) so a hung UCCL case fails fast with a diagnostic instead of hanging the whole node; (2) investigate the UCCL engine connect/destroy lifecycle for a deadlock when starting successive back-to-back runs (the Error/HUP on connection warnings and near-zero throughput suggest the RDMA endpoint from the prior run isn't being cleanly torn down before the next one connects). Reproduce locally by running just the UCCL WRITE DRAM→VRAM case repeatedly.

Related: UCCL history: PR #1271 (63c494e1), PR #1428 (f8e2221f), PR #895 (initial UCCL). CI crash-debug export: PR #1800 (e286b8f7). No existing issue found for this specific hang.

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