Skip to content

feat(health): add NMX-C streaming collector#3121

Open
jayzhudev wants to merge 1 commit into
NVIDIA:mainfrom
jayzhudev:health/nmx-c-streaming
Open

feat(health): add NMX-C streaming collector#3121
jayzhudev wants to merge 1 commit into
NVIDIA:mainfrom
jayzhudev:health/nmx-c-streaming

Conversation

@jayzhudev

@jayzhudev jayzhudev commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Add NMX-C Subscribe streaming support to carbide-health.

This collector connects to eligible primary switch-host NMX-C gRPC endpoints, validates the Subscribe acknowledgement, and emits NMX-C notifications as structured log events through tracing, log-file, and OTLP sinks.

Note: Security and mTLS support are required for all switch host collectors, but are outside of the scope of this PR. They are tracked by #3122 and will be addressed in separate PRs.

Related issues

Closes #3120

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

Manual test logs from updating a primary switch's topology and restarting the cluster:

level=INFO msg="Started NMX-C streaming collection for switch endpoint" endpoint_key=<SWITCH_ENDPOINT> total_nmxc_collectors=1
level=INFO msg="streaming collector connecting" collector_type=nmxc endpoint=<SWITCH_HOST>
level=INFO msg="streaming collector connected" collector_type=nmxc endpoint=<SWITCH_HOST>

level=INFO msg="Log event" collector=nmxc component_type=nvlink_switch endpoint=<SWITCH_ENDPOINT> severity=INFO body="NMX-C stream notification received: subscription_response domain_uuid=<DOMAIN_UUID> app_uuid=<APP_UUID> app_ver=<APP_VERSION> return_code=NMX_ST_SUCCESS notification_payload={...}"
level=INFO msg="Log event" collector=nmxc component_type=nvlink_switch endpoint=<SWITCH_ENDPOINT> severity=INFO body="NMX-C stream notification received: static_config_response domain_uuid=<DOMAIN_UUID> app_uuid=<APP_UUID> app_ver=<APP_VERSION> return_code=NMX_ST_SUCCESS notification_payload={... \"config_file_name\":\"fm_config\",\"key\":\"MNNVL_TOPOLOGY\",\"value\":\"gb200_nvl36r1_c2g4_topology\" ...}"
level=INFO msg="Log event" collector=nmxc component_type=nvlink_switch endpoint=<SWITCH_ENDPOINT> severity=INFO body="NMX-C stream notification received: static_config_response domain_uuid=<DOMAIN_UUID> app_uuid=<APP_UUID> app_ver=<APP_VERSION> return_code=NMX_ST_SUCCESS notification_payload={... \"config_file_name\":\"fm_config\",\"key\":\"MNNVL_TOPOLOGY\",\"value\":\"gb200_nvl72r1_c2g4_topology\" ...}"

level=INFO msg="streaming collector stream ended, reconnecting" collector_type=nmxc endpoint=<SWITCH_HOST>
level=ERROR msg="streaming collector connection failed" collector_type=nmxc endpoint=<SWITCH_HOST> error="NMX-C connect failed for http://<SWITCH_HOST_IP>:9370: transport error"
level=INFO msg="streaming collector connecting" collector_type=nmxc endpoint=<SWITCH_HOST>
level=INFO msg="streaming collector connected" collector_type=nmxc endpoint=<SWITCH_HOST>

level=INFO msg="Log event" collector=nmxc component_type=nvlink_switch endpoint=<SWITCH_ENDPOINT> severity=INFO body="NMX-C stream notification received: subscription_response domain_uuid=<DOMAIN_UUID> app_uuid=<APP_UUID> app_ver=<APP_VERSION> return_code=NMX_ST_SUCCESS notification_payload={...}"

@jayzhudev jayzhudev self-assigned this Jul 5, 2026
@jayzhudev jayzhudev requested review from a team and polarweasel as code owners July 5, 2026 03:20
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added support for NMX-C streaming collection for eligible switch-host endpoints.
    • Expanded health configuration options for NMX-C connectivity, retries, heartbeats, and timeouts.
    • Improved detection of eligible switch endpoints for collection based on role and primary status.
  • Bug Fixes

    • Streaming collectors can now report connection failures while still delivering any generated log events.
  • Documentation

    • Updated monitoring documentation with NMX-C setup and connection details.

Walkthrough

Adds NMX-C streaming collection for eligible switch-host endpoints, including runtime contract updates, collector implementation, config and discovery wiring, endpoint metadata propagation, cleanup handling, and documentation.

Changes

NMX-C streaming collector feature

Layer / File(s) Summary
Streaming connect contract
crates/health/src/collectors/runtime.rs, crates/health/src/collectors/logs/sse.rs
Adds StreamingConnectResult with connected and pre-connected failure outcomes, updates StreamingCollector::connect, and adapts the SSE logs collector to the new contract.
NmxcCollector implementation
crates/health/src/collectors/nmxc.rs, crates/health/src/collectors/mod.rs, crates/health/src/lib.rs
Implements NmxcCollector and NmxcCollectorConfig, the Hello/Subscribe handshake, notification mapping, log shaping, transport/timeout errors, module exports, and the NMX-C RPC error variant.
NMX-C config schema
crates/health/src/config.rs, crates/health/example/config.example.toml
Adds NMX-C collector config, validation, sink log-event detection, static switch enablement, example TOML updates, and config tests for defaults, parsing, and validation.
SwitchData nmxc_enabled propagation
crates/health/src/endpoint/model.rs, crates/health/src/endpoint/sources.rs, crates/health/src/api_client.rs, crates/health/src/otlp/convert.rs, crates/health/src/sink/*, crates/health/src/collectors/nvue/gnmi/*
Adds nmxc_enabled to SwitchData, derives it from static/API endpoint sources, and updates test fixtures that construct switch metadata.
Discovery collector state
crates/health/src/discovery/context.rs
Adds CollectorKind::Nmxc, an nmxc collector map, and nmxc_config/log_event_sink_enabled on discovery context.
NMX-C eligibility and spawn
crates/health/src/discovery/spawn.rs
Adds the NMX-C subscription eligibility helper, spawns NmxcCollector for eligible switch hosts, and updates spawn test helpers and assertions.
Discovery iteration cleanup
crates/health/src/discovery/iteration.rs, crates/health/src/discovery/cleanup.rs
Adds NMX-C eligibility-key computation, generalizes collector stop messaging, wires NMX-C cleanup into discovery iteration, and adds cleanup tests.
NMX-C documentation
docs/operations/monitoring-health.md
Documents NMX-C as an optional collector and adds configuration and transport notes.

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

Sequence Diagram(s)

sequenceDiagram
  participant Discovery as Discovery iteration
  participant Spawn as Collector spawn
  participant Collector as NmxcCollector
  participant Gateway as NMX-C gRPC gateway
  participant Sink as Log sink

  Discovery->>Spawn: eligible endpoint set
  Spawn->>Spawn: switch_supports_nmxc_subscription()
  Spawn->>Collector: start_streaming()
  Collector->>Gateway: Hello
  Gateway-->>Collector: server header
  Collector->>Gateway: Subscribe
  Gateway-->>Collector: initial acknowledgement
  alt Rejected acknowledgement
    Collector->>Sink: emit rejection log
    Collector-->>Spawn: NmxcStatus error
  else Accepted acknowledgement
    Collector-->>Spawn: connected stream
    loop notifications
      Gateway-->>Collector: notification
      Collector->>Sink: emit structured log
    end
  end
  Discovery->>Discovery: stop ineligible collectors
Loading

Related issues: #3120
Suggested labels: enhancement, health, streaming, discovery
Suggested reviewers: Health crate maintainers familiar with discovery and streaming collectors.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: adding an NMX-C streaming collector to health.
Description check ✅ Passed The description is directly aligned with the NMX-C streaming collector feature and its documented scope.
Linked Issues check ✅ Passed The PR implements the requested NMX-C config, eligibility, streaming, reconnect, logging, and documentation requirements from #3120.
Out of Scope Changes check ✅ Passed The changes appear scoped to NMX-C support, related tests, and documentation, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
crates/health/src/config.rs (1)

1856-1891: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer value_scenarios! for this total-operation test.

test_sinks_config_includes_log_events hand-rolls a case array and loop, while the sibling test_nmxc_transport_config_validation added in this same PR correctly uses value_scenarios! for an equivalent total-operation table. Since includes_log_events() is a total (non-fallible) operation, value_scenarios! would give per-case failure labeling for free and keep style consistent within the diff.

As per coding guidelines, "Prefer table-driven tests using the carbide-test-support crate with scenarios! for fallible operations and value_scenarios! for total operations."

♻️ Proposed refactor using value_scenarios!
-    fn test_sinks_config_includes_log_events() {
-        let cases = [
-            ("default", SinksConfig::default(), false),
-            (
-                "tracing",
-                SinksConfig {
-                    tracing: Configurable::Enabled(TracingSinkConfig::default()),
-                    ..SinksConfig::default()
-                },
-                true,
-            ),
-            ...
-        ];
-
-        for (name, sinks, expected) in cases {
-            assert_eq!(sinks.includes_log_events(), expected, "{name}");
-        }
+    fn test_sinks_config_includes_log_events() {
+        value_scenarios!(
+            run = |sinks: SinksConfig| sinks.includes_log_events();
+
+            "sinks includes_log_events" {
+                SinksConfig::default() => false,
+                SinksConfig { tracing: Configurable::Enabled(TracingSinkConfig::default()), ..SinksConfig::default() } => true,
+                SinksConfig { log_file: Configurable::Enabled(LogFileSinkConfig::default()), ..SinksConfig::default() } => true,
+                SinksConfig { otlp: Configurable::Enabled(OtlpSinkConfig::default()), ..SinksConfig::default() } => true,
+            }
+        );
     }
🤖 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 `@crates/health/src/config.rs` around lines 1856 - 1891, Refactor
test_sinks_config_includes_log_events to use value_scenarios! instead of the
manual cases array and for-loop, since includes_log_events() is a total
operation. Keep the same scenario coverage for default, tracing, log-file, and
otlp by expressing them as value scenarios, and preserve the per-case assertion
labeling style used by test_nmxc_transport_config_validation for consistency.

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 `@crates/health/src/collectors/nmxc.rs`:
- Around line 227-241: Add client-side HTTP/2 keepalive to the nmxc_client
Endpoint configuration so dead NMX-C peers are detected even after the initial
connect. Update the Endpoint builder in nmxc_client to set keepalive settings
before connect(), using the existing connect_timeout flow and preserving the
current error mapping via nmxc_transport_error. This should ensure long-lived
streams fail as transport errors instead of leaving stream.next() pending
indefinitely.

In `@docs/operations/monitoring-health.md`:
- Around line 146-148: The NMX-C collection description uses imprecise transport
wording; update the monitoring-health docs text to match the gRPC transport used
by the collector. In the relevant prose section, replace “plaintext HTTP only”
with wording that clearly says it uses plaintext gRPC over HTTP/2, and keep the
note that TLS, certificate bypass, custom certificate loading, and mTLS are out
of scope and not represented by NICo API SPIFFE certificate fields.

---

Nitpick comments:
In `@crates/health/src/config.rs`:
- Around line 1856-1891: Refactor test_sinks_config_includes_log_events to use
value_scenarios! instead of the manual cases array and for-loop, since
includes_log_events() is a total operation. Keep the same scenario coverage for
default, tracing, log-file, and otlp by expressing them as value scenarios, and
preserve the per-case assertion labeling style used by
test_nmxc_transport_config_validation for consistency.
🪄 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: CHILL

Plan: Enterprise

Run ID: 2e4dfb14-4224-4918-8be0-3d365b830e5d

📥 Commits

Reviewing files that changed from the base of the PR and between 7308bce and 8217076.

📒 Files selected for processing (20)
  • crates/health/example/config.example.toml
  • crates/health/src/api_client.rs
  • crates/health/src/collectors/logs/sse.rs
  • crates/health/src/collectors/mod.rs
  • crates/health/src/collectors/nmxc.rs
  • crates/health/src/collectors/nvue/gnmi/on_change_processor.rs
  • crates/health/src/collectors/nvue/gnmi/sample_processor.rs
  • crates/health/src/collectors/runtime.rs
  • crates/health/src/config.rs
  • crates/health/src/discovery/cleanup.rs
  • crates/health/src/discovery/context.rs
  • crates/health/src/discovery/iteration.rs
  • crates/health/src/discovery/spawn.rs
  • crates/health/src/endpoint/model.rs
  • crates/health/src/endpoint/sources.rs
  • crates/health/src/lib.rs
  • crates/health/src/otlp/convert.rs
  • crates/health/src/sink/events.rs
  • crates/health/src/sink/prometheus.rs
  • docs/operations/monitoring-health.md

Comment thread crates/health/src/collectors/nmxc.rs
Comment thread docs/operations/monitoring-health.md Outdated
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 270 5 25 89 7 144
machine-validation-runner 771 25 207 278 40 221
machine_validation 771 25 207 278 40 221
machine_validation-aarch64 771 25 207 278 40 221
nvmetal-carbide 771 25 207 278 40 221
TOTAL 3360 105 853 1207 167 1028

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

Signed-off-by: Jay Zhu <jayzhu@nvidia.com>
@jayzhudev jayzhudev force-pushed the health/nmx-c-streaming branch from 8217076 to 44aecdc Compare July 5, 2026 04:47

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

🧹 Nitpick comments (1)
crates/health/src/collectors/runtime.rs (1)

473-505: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate failure-handling logic between Err(e) and Failed{...} arms.

Both arms increment metrics.reconnections_total, emit the same tracing::error! message, and call on_connect_result(Err(&error)) with identical control flow — only the event-forwarding step differs. Consider extracting the common tail (metric bump, log, callback+early-return) into a small helper so the two failure paths can't drift (e.g. one arm's log message changing without the other).

♻️ Suggested consolidation
-                match stream_result {
-                    Err(e) => {
-                        metrics.reconnections_total.inc();
-                        tracing::error!(
-                            error = ?e,
-                            collector_type,
-                            endpoint = ?endpoint.addr,
-                            "streaming collector connection failed"
-                        );
-                        if !on_connect_result(Err(&e)) {
-                            return;
-                        }
-                    }
-                    Ok(StreamingConnectResult::Failed { events, error }) => {
-                        metrics.reconnections_total.inc();
-
-                        for event in events {
-                            metrics.items_processed_total.inc();
-                            data_sink.handle_event(&event_context, &event);
-                        }
-
-                        tracing::error!(
-                            error = ?error,
-                            collector_type,
-                            endpoint = ?endpoint.addr,
-                            "streaming collector connection failed"
-                        );
-
-                        if !on_connect_result(Err(&error)) {
-                            return;
-                        }
-                    }
+                match stream_result {
+                    Err(error) | Ok(StreamingConnectResult::Failed { error, events: _ }) => {
+                        // NOTE: illustrative only — a real fix needs a helper fn since match
+                        // arms with differing bindings can't be merged this simply; extract
+                        // a `handle_connect_failure(&metrics, &data_sink, &event_context, ...)` helper instead.
+                    }
                     Ok(StreamingConnectResult::Connected(mut stream)) => {
🤖 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 `@crates/health/src/collectors/runtime.rs` around lines 473 - 505, The `Err(e)`
and `StreamingConnectResult::Failed { .. }` branches in `runtime.rs` duplicate
the same reconnection metric, error logging, and `on_connect_result`
early-return flow. Extract that shared failure tail into a small helper near the
streaming connection handling logic, and have both `match` arms call it after
their branch-specific work (including the `events` forwarding in `Failed`). Keep
the helper aligned with the existing `metrics.reconnections_total`,
`tracing::error!`, and `on_connect_result` behavior so future changes only need
to happen in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/health/src/collectors/runtime.rs`:
- Around line 473-505: The `Err(e)` and `StreamingConnectResult::Failed { .. }`
branches in `runtime.rs` duplicate the same reconnection metric, error logging,
and `on_connect_result` early-return flow. Extract that shared failure tail into
a small helper near the streaming connection handling logic, and have both
`match` arms call it after their branch-specific work (including the `events`
forwarding in `Failed`). Keep the helper aligned with the existing
`metrics.reconnections_total`, `tracing::error!`, and `on_connect_result`
behavior so future changes only need to happen in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ca6a65ba-1023-49f2-ae12-daa4c3dbffc8

📥 Commits

Reviewing files that changed from the base of the PR and between 8217076 and 44aecdc.

📒 Files selected for processing (20)
  • crates/health/example/config.example.toml
  • crates/health/src/api_client.rs
  • crates/health/src/collectors/logs/sse.rs
  • crates/health/src/collectors/mod.rs
  • crates/health/src/collectors/nmxc.rs
  • crates/health/src/collectors/nvue/gnmi/on_change_processor.rs
  • crates/health/src/collectors/nvue/gnmi/sample_processor.rs
  • crates/health/src/collectors/runtime.rs
  • crates/health/src/config.rs
  • crates/health/src/discovery/cleanup.rs
  • crates/health/src/discovery/context.rs
  • crates/health/src/discovery/iteration.rs
  • crates/health/src/discovery/spawn.rs
  • crates/health/src/endpoint/model.rs
  • crates/health/src/endpoint/sources.rs
  • crates/health/src/lib.rs
  • crates/health/src/otlp/convert.rs
  • crates/health/src/sink/events.rs
  • crates/health/src/sink/prometheus.rs
  • docs/operations/monitoring-health.md
✅ Files skipped from review due to trivial changes (4)
  • crates/health/src/sink/events.rs
  • crates/health/src/collectors/nvue/gnmi/sample_processor.rs
  • crates/health/src/sink/prometheus.rs
  • crates/health/src/otlp/convert.rs
🚧 Files skipped from review as they are similar to previous changes (15)
  • crates/health/src/lib.rs
  • crates/health/src/collectors/logs/sse.rs
  • docs/operations/monitoring-health.md
  • crates/health/src/collectors/nvue/gnmi/on_change_processor.rs
  • crates/health/src/api_client.rs
  • crates/health/src/endpoint/sources.rs
  • crates/health/src/discovery/cleanup.rs
  • crates/health/src/endpoint/model.rs
  • crates/health/example/config.example.toml
  • crates/health/src/collectors/mod.rs
  • crates/health/src/collectors/nmxc.rs
  • crates/health/src/discovery/iteration.rs
  • crates/health/src/config.rs
  • crates/health/src/discovery/context.rs
  • crates/health/src/discovery/spawn.rs

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.

feat: Add NMX-C Subscribe streaming collector to health

1 participant