Skip to content

feat: count firmware update outcomes across every target#3319

Open
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3175-firmware
Open

feat: count firmware update outcomes across every target#3319
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3175-firmware

Conversation

@chet

@chet chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Firmware updates run for three targets -- host BIOS/BMC, DPU NICs, and SuperNICs -- and none of their outcomes are countable today: starts, completions, and failures live only in log lines, including the "wrong version after attempted update" case that means an update silently didn't take. This makes all of it countable, with each event replacing its site's log line at the historical level (info for progress, warn for failures) and the same information as structured fields.

Two counters from carbide-instrument:

  • carbide_firmware_updates_total{target, phase} -- started and completed, per target. For hosts both phases fire, so the started-to-completed gap is the in-flight backlog; the DPU-NIC and SuperNIC targets emit started only, with completion visible in their own state flows.
  • carbide_firmware_update_failures_total{target, cause} -- no_update_match (a DPU left ready between snapshot and trigger) and wrong_version_after_update (the update ran and the firmware version still doesn't match -- the silent-failure case).

Notable details:

  • Started counts only committed triggers: the DPU-NIC emit fires after txn.commit(), so an update whose trigger rolled back is a no_update_match failure, never a started update, and the started-minus-completed math stays truthful.
  • Host completion emits once per machine as its update markers clear, replacing one Vec-debug log line with per-machine lines.
  • A SuperNIC re-counts one started per scout pass while a device stays mismatched -- each pass issues a real apply command, and the sustained rate is the stuck-device signal.

Tests added!

This supports #3175

@chet chet requested review from a team and polarweasel as code owners July 9, 2026 17:15
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! I'll perform a full review of the PR now.

(๑>ᴗ<๑)

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e54c3372-f76d-4f53-a7ea-bc08e8328bbb

📥 Commits

Reviewing files that changed from the base of the PR and between e7a297c and 9d4bc54.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Summary by CodeRabbit

  • New Features
    • Added structured firmware-update observability for Host, DPU NIC, and SuperNIC, emitting progress (Started, Completed) and failure events.
    • Failure events now include explicit causes (no matching update found, wrong version after update), with wrong-version failures deduplicated to reduce repeated noise.
    • SuperNIC progress includes detailed version-mismatch context.
  • Documentation
    • Documented carbide_firmware_updates_total and carbide_firmware_update_failures_total in core metrics.
  • Tests
    • Updated tests to validate emitted event/log output and metric counter changes.

Walkthrough

This PR adds structured firmware-update metrics, emits progress and failure events from host, DPU NIC, and SuperNic update paths, updates tests to assert the new counters, and documents the added observability metrics.

Changes

Firmware update instrumentation

Layer / File(s) Summary
Metrics event types and counters
crates/api-core/src/machine_update_manager/metrics.rs
Defines firmware-update target, phase, and failure-cause enums plus progress and failure events, with unit tests covering emitted logs and counter deltas.
Host and SuperNic progress emission
crates/api-core/src/handlers/host_reprovisioning.rs, crates/api-core/src/handlers/svpc.rs, crates/api-core/src/machine_update_manager/host_firmware.rs
Host reprovisioning and SuperNic firmware application now emit FirmwareUpdateProgress events instead of info logs, using Started phase and target-specific detail fields.
DPU NIC update progress and failures
crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs, crates/api-core/src/tests/dpu_nic_firmware.rs
DpuNicFirmwareUpdate adds wrong-version tracking state, emits FirmwareUpdateFailed and FirmwareUpdateProgress events in start_updates and clear_completed_updates, and initializes the new state in new(). The test captures metrics and asserts the new counter behavior.
Metrics documentation
docs/observability/core_metrics.md
Documents the two new firmware-update counters.

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

Sequence Diagram(s)

sequenceDiagram
  participant HostReprovisioning
  participant DpuNicFirmwareUpdate
  participant SvpcHandler
  participant MetricsModule

  HostReprovisioning->>MetricsModule: emit FirmwareUpdateProgress(Host, Started)
  DpuNicFirmwareUpdate->>MetricsModule: emit FirmwareUpdateProgress(DpuNic, Started)
  DpuNicFirmwareUpdate->>MetricsModule: emit FirmwareUpdateFailed(DpuNic, NoUpdateMatch/WrongVersionAfterUpdate)
  SvpcHandler->>MetricsModule: emit FirmwareUpdateProgress(SuperNic, Started)
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the new firmware update outcome counters across all targets.
Description check ✅ Passed The description is directly aligned with the changes, covering the new metrics, targets, failure causes, and tests.
Docstring Coverage ✅ Passed Docstring coverage is 94.44% which is sufficient. The required threshold is 80.00%.
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.

@github-actions

github-actions Bot commented Jul 9, 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

Caution

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

⚠️ Outside diff range comments (1)
crates/api-core/src/machine_update_manager/host_firmware.rs (1)

70-115: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move the Started emit after the batch commit.
FirmwareUpdateProgress { phase: Started, .. } is emitted before txn.commit().await?, so a later error rolls back the DB work but leaves the counter incremented. Emit from updates_started only after the commit succeeds, matching the DPU path and keeping the series aligned with committed triggers.

🤖 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/api-core/src/machine_update_manager/host_firmware.rs` around lines 70
- 115, The `FirmwareUpdateProgress` with phase `Started` is emitted too early in
`host_firmware::check_for_updates`, before `txn.commit().await?`, which can
leave progress counters out of sync if the transaction later fails. Move the
`carbide_instrument::emit` call so it happens only after `txn.commit()`
succeeds, and emit using the `updates_started` set after the batch is durably
committed; keep `db::host_machine_update::trigger_host_reprovisioning_request`,
`updates_started`, and the commit boundary aligned with the DPU path.
🤖 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/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 117-137: The `clear_completed_updates` path in
`MachineUpdateManager` emits `FirmwareUpdateProgress` with
`FirmwareUpdatePhase::Completed` before the surrounding transaction is
committed, so move those emits out of the transaction boundary. Collect the
affected machine IDs in `clear_completed_updates` while still calling
`remove_health_report` and `update_update_complete`, then have
`MachineUpdateManager::run` emit the `Completed` events only after
`txn.commit().await?` succeeds.

In `@docs/observability/core_metrics.md`:
- Line 44: Update the documentation row for carbide_firmware_updates_total in
the core metrics table to include the phase label alongside target. The current
description only mentions the update target, so clarify that the counter is
split by both target and phase, and note the started/completed series in the
metric description.

---

Outside diff comments:
In `@crates/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 70-115: The `FirmwareUpdateProgress` with phase `Started` is
emitted too early in `host_firmware::check_for_updates`, before
`txn.commit().await?`, which can leave progress counters out of sync if the
transaction later fails. Move the `carbide_instrument::emit` call so it happens
only after `txn.commit()` succeeds, and emit using the `updates_started` set
after the batch is durably committed; keep
`db::host_machine_update::trigger_host_reprovisioning_request`,
`updates_started`, and the commit boundary aligned with the DPU path.
🪄 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: 77a83aa7-0cb7-4e16-9f30-ca4a55aa12ac

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and 8188e48.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Comment thread crates/api-core/src/machine_update_manager/host_firmware.rs
Comment thread docs/observability/core_metrics.md 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)
crates/api-core/src/machine_update_manager/host_firmware.rs (1)

89-114: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Emit Started after the batch commit, not per-iteration inside it.

txn.commit().await? at line 113 covers the whole loop. If a later trigger_host_reprovisioning_request in the same batch fails and returns Err, the transaction rolls back — undoing earlier machines' DB writes — but their FirmwareUpdateProgress{Started} events already fired. Those machines get re-triggered (and re-counted) on the next pass, inflating carbide_firmware_updates_total{target=host,phase=started} beyond real distinct starts. dpu_nic_firmware.rs's start_updates avoids this exact pitfall by committing before emitting; apply the same ordering here.

🛡️ Proposed fix: commit before emitting
             db::host_machine_update::trigger_host_reprovisioning_request(
                 &mut txn,
                 "Automated",
                 machine_update,
             )
             .await?;

-            // Counted after the trigger succeeds; the commit below spans the
-            // whole batch, so a later DB error can re-count machines on the
-            // next pass -- the same repeat-on-retry the old log line had.
-            carbide_instrument::emit(FirmwareUpdateProgress {
-                target: FirmwareUpdateTarget::Host,
-                phase: FirmwareUpdatePhase::Started,
-                machine_id: *machine_update,
-                detail: String::new(),
-            });
             updates_started.insert(*machine_update);
         }

         txn.commit().await?;
+        for machine_update in &updates_started {
+            carbide_instrument::emit(FirmwareUpdateProgress {
+                target: FirmwareUpdateTarget::Host,
+                phase: FirmwareUpdatePhase::Started,
+                machine_id: *machine_update,
+                detail: String::new(),
+            });
+        }
         Ok(updates_started)
🤖 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/api-core/src/machine_update_manager/host_firmware.rs` around lines 89
- 114, Emitting FirmwareUpdateProgress::Started inside the loop in
host_firmware::start_updates can overcount when the surrounding transaction
rolls back; move the start-event emission to after txn.commit().await? succeeds,
so only committed host reprovision requests are counted. Keep the
trigger_host_reprovisioning_request loop and updates_started collection as-is,
but defer carbide_instrument::emit for each machine until after the batch
commit, following the same commit-before-emit ordering used in
dpu_nic_firmware::start_updates.
🤖 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 `@crates/api-core/src/machine_update_manager/host_firmware.rs`:
- Around line 89-114: Emitting FirmwareUpdateProgress::Started inside the loop
in host_firmware::start_updates can overcount when the surrounding transaction
rolls back; move the start-event emission to after txn.commit().await? succeeds,
so only committed host reprovision requests are counted. Keep the
trigger_host_reprovisioning_request loop and updates_started collection as-is,
but defer carbide_instrument::emit for each machine until after the batch
commit, following the same commit-before-emit ordering used in
dpu_nic_firmware::start_updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0c1245b1-1e70-47dc-ac43-0cb7f7f534e3

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and 8188e48.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

@chet chet force-pushed the gh-issue-3175-firmware branch from 8188e48 to b22549d Compare July 9, 2026 18:05
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! I'll kick off a full review of the PR now.

✅ Action performed

Full review finished.

@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/api-core/src/machine_update_manager/metrics.rs (1)

113-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider Option<String> instead of empty-string sentinels for optional context.

unmatched_dpu_machine_id, firmware_version, and detail all rely on an empty String to mean "not applicable for this cause/target." This is a workable convention documented in the field comments, but it's a magic value that a future caller could misuse (e.g., accidentally logging a genuinely empty version string as "n/a"). Since these are #[context] fields (log-only, not labels), an Option<String> would let carbide_instrument render None explicitly and remove the ambiguity, at the cost of updating the three call sites that currently pass String::new().

♻️ Sketch of the alternative
-    #[context]
-    pub unmatched_dpu_machine_id: String,
+    #[context]
+    pub unmatched_dpu_machine_id: Option<String>,

Also applies to: 150-167

🤖 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/api-core/src/machine_update_manager/metrics.rs` around lines 113 -
127, The `FirmwareUpdateProgress` context fields are using empty strings as
sentinel values for “not applicable,” which is ambiguous and easy to misuse.
Update the `FirmwareUpdateProgress` struct fields (`machine_id`/`detail` and the
related optional context fields used in the same module) to use `Option<String>`
for log-only `#[context]` data, then adjust the call sites that currently pass
`String::new()` to pass `None` when a value is not relevant. Keep the
`FirmwareUpdateProgress` and `carbide_instrument` usage aligned so the logged
context renders the absence explicitly.
🤖 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/api-core/src/machine_update_manager/metrics.rs`:
- Around line 113-127: The `FirmwareUpdateProgress` context fields are using
empty strings as sentinel values for “not applicable,” which is ambiguous and
easy to misuse. Update the `FirmwareUpdateProgress` struct fields
(`machine_id`/`detail` and the related optional context fields used in the same
module) to use `Option<String>` for log-only `#[context]` data, then adjust the
call sites that currently pass `String::new()` to pass `None` when a value is
not relevant. Keep the `FirmwareUpdateProgress` and `carbide_instrument` usage
aligned so the logged context renders the absence explicitly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 729a6855-20f3-4989-a465-b1f415714e55

📥 Commits

Reviewing files that changed from the base of the PR and between 356dd0b and b22549d.

📒 Files selected for processing (6)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

@github-actions

github-actions Bot commented Jul 9, 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 271 13 34 91 7 126
machine-validation-runner 800 37 234 291 43 195
machine_validation 800 37 234 291 43 195
machine_validation-aarch64 800 37 234 291 43 195
nvmetal-carbide 800 37 234 291 43 195
TOTAL 3477 161 970 1261 179 906

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

@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks! (External Codex round: a persistent wrong-version condition now counts once per outcome instead of once per manager pass -- an in-memory reported-set, cleared when the DPU's markers clear so a fresh attempt re-reports; and operator-initiated host reprovisions emit the paired started, so the started-to-completed gap stays truthful for every initiator.)

@chet chet force-pushed the gh-issue-3175-firmware branch from b22549d to f1b2160 Compare July 9, 2026 20:24
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet Understood. I’ll run a fresh full review with specific attention to the corrected semantics: deduplicating persistent wrong-version reporting to once per outcome until DPU markers clear, and ensuring operator-initiated host reprovisions emit the paired started signal so the started-to-completed gap remains accurate for every initiator.

(ᵔᴥᵔ)


✏️ Learnings added
Learnt from: chet
URL: https://github.com/NVIDIA/infra-controller/pull/3319

Timestamp: 2026-07-09T20:25:21.005Z
Learning: In the firmware update flow for this repository, a persistent wrong-version-after-update condition should be counted once per outcome rather than once per manager pass. The implementation uses an in-memory reported set that is cleared when the DPU's markers clear, so a fresh attempt can be reported again.

Learnt from: chet
URL: https://github.com/NVIDIA/infra-controller/pull/3319

Timestamp: 2026-07-09T20:25:21.005Z
Learning: In the host firmware reprovision flow for this repository, operator-initiated host reprovisions must emit the paired `started` firmware-update metric so that the `started` to `completed` gap remains truthful across every initiation path.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Full review finished.

@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)
crates/api-core/src/handlers/host_reprovisioning.rs (1)

76-92: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Emit occurs before the transaction commits.

The carbide_instrument::emit call at Lines 85-92 fires immediately after trigger_host_reprovisioning_request, but the enclosing transaction is not committed until Line 100. If txn.commit() fails, a Started event and counter increment will have already been recorded for a reprovisioning request that was never actually persisted — undermining the started-to-completed gap accuracy that this PR is explicitly designed to preserve.

Move the emit after the successful txn.commit() (or only within the Mode::Set branch, after commit) so metrics only reflect durably-committed state.

🐛 Proposed fix
     match req.mode() {
         Mode::Set => {
             let initiator = req.initiator().as_str_name();
             db::host_machine_update::trigger_host_reprovisioning_request(
                 &mut txn,
                 initiator,
                 &machine_id,
             )
             .await?;
-            // Manual initiations pair with the same completion emit the
-            // update manager's automatic path gets, keeping the
-            // started-to-completed gap truthful for every initiator.
-            carbide_instrument::emit(
-                crate::machine_update_manager::metrics::FirmwareUpdateProgress {
-                    target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
-                    phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
-                    machine_id,
-                    detail: initiator.to_string(),
-                },
-            );
         }
         Mode::Clear => {
             db::host_machine_update::clear_host_reprovisioning_request(&mut txn, &machine_id)
                 .await?;
         }
     }
 
     txn.commit().await?;
+
+    if matches!(req.mode(), Mode::Set) {
+        carbide_instrument::emit(
+            crate::machine_update_manager::metrics::FirmwareUpdateProgress {
+                target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
+                phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
+                machine_id,
+                detail: String::new(),
+            },
+        );
+    }
 
     Ok(Response::new(()))
🤖 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/api-core/src/handlers/host_reprovisioning.rs` around lines 76 - 92,
The Started metrics emit in host reprovisioning is happening before the
transaction is durably committed, so a failed commit can still record a request
that never persisted. Update host_reprovisioning.rs in the flow around
trigger_host_reprovisioning_request and txn.commit() so carbide_instrument::emit
runs only after a successful commit, or guard it so it executes only in the
committed Mode::Set path. Keep the metrics emission aligned with the committed
state by referencing the existing FirmwareUpdateProgress,
FirmwareUpdateTarget::Host, and FirmwareUpdatePhase::Started logic.
🤖 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/api-core/src/handlers/host_reprovisioning.rs`:
- Line 90: The `HostReprovisioning` handler is populating
`FirmwareUpdateProgress::detail` with `initiator.to_string()`, which conflicts
with the Host contract and existing Host test expectations. Update the
`host_reprovisioning` flow to keep `detail` empty for Host emissions, using the
`FirmwareUpdateProgress` construction in this handler; if initiator info must be
preserved, first update the documented Host schema and related tests to match
the new contract.

---

Outside diff comments:
In `@crates/api-core/src/handlers/host_reprovisioning.rs`:
- Around line 76-92: The Started metrics emit in host reprovisioning is
happening before the transaction is durably committed, so a failed commit can
still record a request that never persisted. Update host_reprovisioning.rs in
the flow around trigger_host_reprovisioning_request and txn.commit() so
carbide_instrument::emit runs only after a successful commit, or guard it so it
executes only in the committed Mode::Set path. Keep the metrics emission aligned
with the committed state by referencing the existing FirmwareUpdateProgress,
FirmwareUpdateTarget::Host, and FirmwareUpdatePhase::Started logic.
🪄 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: f2b4cedb-6c86-46c3-a6da-dc8653885574

📥 Commits

Reviewing files that changed from the base of the PR and between b22549d and f1b2160.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md
✅ Files skipped from review due to trivial changes (1)
  • docs/observability/core_metrics.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs

Comment thread crates/api-core/src/handlers/host_reprovisioning.rs

@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 (2)
crates/api-core/src/handlers/host_reprovisioning.rs (1)

85-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a top-level use over repeated fully-qualified paths.

Other call sites (host_firmware.rs, dpu_nic_firmware.rs, svpc.rs) import FirmwareUpdateProgress/FirmwareUpdateTarget/FirmwareUpdatePhase at the top of the file; this handler instead spells out the full module path three times inline, which is more verbose than necessary.

♻️ Proposed refactor
+use crate::machine_update_manager::metrics::{
+    FirmwareUpdatePhase, FirmwareUpdateProgress, FirmwareUpdateTarget,
+};
+
 pub(crate) async fn trigger_host_reprovisioning(
     ...
-            carbide_instrument::emit(
-                crate::machine_update_manager::metrics::FirmwareUpdateProgress {
-                    target: crate::machine_update_manager::metrics::FirmwareUpdateTarget::Host,
-                    phase: crate::machine_update_manager::metrics::FirmwareUpdatePhase::Started,
-                    machine_id,
-                    detail: initiator.to_string(),
-                },
-            );
+            carbide_instrument::emit(FirmwareUpdateProgress {
+                target: FirmwareUpdateTarget::Host,
+                phase: FirmwareUpdatePhase::Started,
+                machine_id,
+                detail: String::new(),
+            });
🤖 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/api-core/src/handlers/host_reprovisioning.rs` around lines 85 - 92,
The host reprovisioning handler is using repeated fully-qualified paths for
firmware update metrics, making the `emit` call more verbose than the other
firmware handlers. Add top-level `use` imports for `FirmwareUpdateProgress`,
`FirmwareUpdateTarget`, and `FirmwareUpdatePhase` in `host_reprovisioning.rs`,
then update the `carbide_instrument::emit` call to use those imported symbols
consistently like the other call sites.
crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs (1)

262-282: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: Default::default() avoids repeating the mutex boilerplate.

reported_wrong_versions: std::sync::Mutex::new(HashSet::new()) is spelled out identically here and in five test struct literals in tests/dpu_nic_firmware.rs. Mutex<HashSet<T>> implements Default, so Default::default() would be slightly more idiomatic and DRY.

♻️ Optional tweak
         Some(DpuNicFirmwareUpdate {
-            reported_wrong_versions: std::sync::Mutex::new(HashSet::new()),
+            reported_wrong_versions: Default::default(),
             metrics: Some(metrics),
             config,
             dpf,
         })
🤖 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/api-core/src/machine_update_manager/dpu_nic_firmware.rs` around lines
262 - 282, The `DpuNicFirmwareUpdate::new` initializer repeats the mutex
construction boilerplate for `reported_wrong_versions`, and the same pattern
appears in the related test struct literals; switch this field to use
`Default::default()` since `Mutex<HashSet<_>>` implements `Default`, keeping the
constructor and the `tests/dpu_nic_firmware.rs` fixtures consistent and more
idiomatic.
🤖 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/api-core/src/handlers/host_reprovisioning.rs`:
- Around line 85-92: The host reprovisioning handler is using repeated
fully-qualified paths for firmware update metrics, making the `emit` call more
verbose than the other firmware handlers. Add top-level `use` imports for
`FirmwareUpdateProgress`, `FirmwareUpdateTarget`, and `FirmwareUpdatePhase` in
`host_reprovisioning.rs`, then update the `carbide_instrument::emit` call to use
those imported symbols consistently like the other call sites.

In `@crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs`:
- Around line 262-282: The `DpuNicFirmwareUpdate::new` initializer repeats the
mutex construction boilerplate for `reported_wrong_versions`, and the same
pattern appears in the related test struct literals; switch this field to use
`Default::default()` since `Mutex<HashSet<_>>` implements `Default`, keeping the
constructor and the `tests/dpu_nic_firmware.rs` fixtures consistent and more
idiomatic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: afb23cac-cb53-4ce3-9659-6cca1cb36892

📥 Commits

Reviewing files that changed from the base of the PR and between e7a297c and f1b2160.

📒 Files selected for processing (7)
  • crates/api-core/src/handlers/host_reprovisioning.rs
  • crates/api-core/src/handlers/svpc.rs
  • crates/api-core/src/machine_update_manager/dpu_nic_firmware.rs
  • crates/api-core/src/machine_update_manager/host_firmware.rs
  • crates/api-core/src/machine_update_manager/metrics.rs
  • crates/api-core/src/tests/dpu_nic_firmware.rs
  • docs/observability/core_metrics.md

Firmware updates run for three targets -- host BIOS/BMC, DPU NICs, and SuperNICs -- and none of their outcomes are countable today: starts, completions, and failures live only in log lines, including the "wrong version after attempted update" case that means an update silently didn't take. This makes all of it countable, with each event replacing its site's log line at the historical level (info for progress, warn for failures) and the same information as structured fields.

Two counters from `carbide-instrument`:

- `carbide_firmware_updates_total{target, phase}` -- started and completed, per target. For hosts both phases fire, so the started-to-completed gap is the in-flight backlog; the DPU-NIC and SuperNIC targets emit started only, with completion visible in their own state flows.
- `carbide_firmware_update_failures_total{target, cause}` -- `no_update_match` (a DPU left ready between snapshot and trigger) and `wrong_version_after_update` (the update ran and the firmware version still doesn't match -- the silent-failure case).

Notable details:

- Started counts only committed triggers: the DPU-NIC emit fires after `txn.commit()`, so an update whose trigger rolled back is a `no_update_match` failure, never a started update, and the started-minus-completed math stays truthful.
- Host completion emits once per machine as its update markers clear, replacing one Vec-debug log line with per-machine lines.
- A SuperNIC re-counts one started per scout pass while a device stays mismatched -- each pass issues a real apply command, and the sustained rate is the stuck-device signal.

Tests added!

This supports NVIDIA#3175

Signed-off-by: Chet Nichols III <chetn@nvidia.com>
@chet chet force-pushed the gh-issue-3175-firmware branch from f1b2160 to 9d4bc54 Compare July 9, 2026 20:52
@chet

chet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@chet, you're welcome! Kicking off a full review of the PR now.

(๑>ᴗ<๑)

✅ Action performed

Full review finished.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants