Skip to content

feat(health): Emit latency and health metrics for BMC interactions - #4720

Merged
Matthias247 merged 2 commits into
NVIDIA:mainfrom
Matthias247:bmclatency
Aug 11, 2026
Merged

feat(health): Emit latency and health metrics for BMC interactions#4720
Matthias247 merged 2 commits into
NVIDIA:mainfrom
Matthias247:bmclatency

Conversation

@Matthias247

Copy link
Copy Markdown
Contributor

Adds new config file flags to hw-health which make it emit a histogram metric for requests to BMCs. The metric with all properties attached will look like:

carbide_hardware_health_bmc_latency_ms_bucket{http_response_status_code="200",http_request_method="GET",http_path="/redfish/
  v1",server_address="1.3.5.12",url_scheme="https",bmc_vendor="NVIDIA",bmc_model="GB200 BMC",le="100"} 1

The metric can be explicitly enabled via setting

[metrics]
enable_bmc_latency_metrics = true

By default it will contain all available fields. If only a subset of fields should be emitted to reduce the amount of time series, the setting metrics.bmc_latency_attributes can be used to explicitly specify the attributes that should be included. Adding all to the list leads to emitting all attributes (default) again.

[metrics]
bmc_latency_attributes = ["http_response_status_code", "server_address", "url_scheme"]

Limitation:

The status code for success responses are estimated since nv-redfish does not expose it. It is always set to 200 for GET requests and 201 for creation requests. Status codes for error responses are accurate.

Related issues

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

@Matthias247
Matthias247 requested a review from a team as a code owner August 7, 2026 20:36
@coderabbitai

coderabbitai Bot commented Aug 7, 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

Summary by CodeRabbit

  • New Features
    • Added optional BMC request-latency metrics across all endpoint types.
    • Metrics can include request duration, HTTP status, method, path, server address, URL scheme, BMC vendor, model, machine, rack, and entity labels.
    • Added configurable latency buckets and selectable metric attributes.
    • Metrics are disabled by default and can be enabled through configuration.
  • Bug Fixes
    • Added validation to prevent enabling metrics without selected attributes.
    • Unavailable BMC identity information is reported as “unknown.”

Walkthrough

The change adds optional, configurable BMC latency histograms. BmcClient records Redfish request dimensions and durations, captures BMC identity, and receives the shared metrics handle through all endpoint sources.

Changes

BMC latency metrics

Layer / File(s) Summary
Metrics contract and configuration
crates/health/src/metrics.rs, crates/health/src/config.rs, crates/health/example/config.example.toml
Adds selectable latency attributes, histogram buckets, observation recording, configuration defaults, validation, parsing tests, and example settings.
BMC request instrumentation
crates/health/src/bmc.rs
Adds latency recording for Redfish operations, HTTP status classification, endpoint metadata, BMC identity labels, authentication-retry handling, and metric tests.
Endpoint metrics propagation
crates/health/src/lib.rs, crates/health/src/api_client.rs, crates/health/src/endpoint/*, crates/health/src/discovery/spawn.rs
Creates metrics during startup when enabled and propagates the optional handle into static, API, and cluster BmcClient instances. Existing test call sites pass None.

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

Sequence Diagram(s)

sequenceDiagram
  participant HealthService
  participant MetricsRegistry
  participant EndpointSource
  participant BmcClient
  HealthService->>MetricsRegistry: create BmcLatencyMetrics when enabled
  HealthService->>EndpointSource: pass optional metrics handle
  EndpointSource->>BmcClient: construct BmcClient with metrics handle
  BmcClient->>BmcClient: record Redfish request latency and labels
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding BMC latency metrics to health monitoring.
Description check ✅ Passed The description directly explains the new BMC latency metric configuration, labels, limitations, and testing.
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.

@Matthias247

Copy link
Copy Markdown
Contributor Author

Implementation thoughts: This implementation generates the metrics in the Bmc client wrapper. It thereby is limited to the info that the nvredfish exposes via its public interface and involves a fair amount of code changes.

Other approaches we can consider are:

  • Let nvredfish emit the metrics directly (but that binds it to a metrics framework)
  • Let nvredfish emit a tracing::Event with the data required (request type, destination, latency). And install a tracing::Subscriber in health which transforms it into a metric. That might however lack some data that nvredfish might not have (e.g. machine_id or rack_id) and that might be harder to retrieve from the subscriber context. But it would have less code throughout health.

@poroh @yoks Let me know if there's any preference

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

🤖 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/bmc.rs`:
- Around line 790-802: Update http_error_status_code to locate the “HTTP ”
marker and parse only the immediately following three-digit status token,
avoiding unrelated numeric values such as URL IP octets; preserve validation
through http::StatusCode and add a regression test covering a message containing
https://10.0.0.100 alongside HTTP 500.

In `@crates/health/src/metrics.rs`:
- Around line 58-94: Remove BmcLatencyAttribute::ServerAddress from the default
BmcLatencyAttribute::ATTRIBUTES set so the All selector does not automatically
expose unbounded BMC IP addresses as Prometheus labels. Leave the enum and
label_name mapping available for explicit opt-in, while preserving the other
default attributes.
- Around line 113-162: Update BmcLatencyMetrics::new_with_attributes to prevent
BmcLatencyAttribute::All from being stored or used as a concrete label. Filter
All from the attributes before constructing label_names and saving the
attributes, or reject it through the constructor’s existing Result contract;
ensure direct callers cannot cause observe() to reach its unreachable! branch.
- Around line 127-134: Rename the metric constructed in the latency HistogramVec
to use the `{prefix}_bmc_latency_milliseconds` suffix, then update all existing
test and example configuration references to the new name. Extend
`test_integration` to exercise the health endpoint so the generated metric
catalogue includes this metric and its HELP text.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7cd43cfc-4a56-408a-831c-e1525610c00b

📥 Commits

Reviewing files that changed from the base of the PR and between 355618d and d34742e.

📒 Files selected for processing (10)
  • crates/health/example/config.example.toml
  • crates/health/src/api_client.rs
  • crates/health/src/bmc.rs
  • crates/health/src/config.rs
  • crates/health/src/discovery/spawn.rs
  • crates/health/src/endpoint/cluster.rs
  • crates/health/src/endpoint/mod.rs
  • crates/health/src/endpoint/sources.rs
  • crates/health/src/lib.rs
  • crates/health/src/metrics.rs

Comment thread crates/health/src/bmc.rs Outdated
Comment on lines +790 to +802
fn http_error_status_code(message: &str) -> Option<String> {
message
.split(|character: char| !character.is_ascii_digit())
.find_map(|token| {
if token.len() != 3 {
return None;
}
let code = token.parse::<u16>().ok()?;
http::StatusCode::from_u16(code)
.ok()
.map(|status| status.as_u16().to_string())
})
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect how HealthError::HttpError messages are constructed to confirm whether they can embed IPs or other 3-digit numbers before the real status code.
rg -nP -C4 'HttpError\s*\(' --type=rust crates/health

Repository: NVIDIA/infra-controller

Length of output: 17517


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- function and nearby tests ---'
sed -n '780,825p' crates/health/src/bmc.rs
rg -n -C6 'http_error_status_code|error_status_code|http_response_status_code' crates/health/src/bmc.rs

printf '%s\n' '--- focused behavioral probe ---'
python3 - <<'PY'
import re

def status_code_current(message):
    for token in re.split(r'[^0-9]', message):
        if len(token) != 3:
            continue
        code = int(token)
        # http::StatusCode::from_u16 accepts 100..=599.
        if 100 <= code <= 599:
            return str(code)
    return None

def status_code_proposed(message):
    parts = message.split("HTTP ", 1)
    if len(parts) != 2:
        return None
    for token in re.split(r'[^0-9]', parts[1]):
        if len(token) == 3:
            code = int(token)
            if 100 <= code <= 599:
                return str(code)
    return None

cases = [
    "https://10.0.0.100:8443: HTTP 500 for switch 12",
    "https://10.0.0.7:8443: HTTP 500 for switch 12",
    "request failed with HTTP 404",
    "request failed with HTTP 500: body contains 404",
    "request failed with HTTP 500: retry 404",
    "request failed without a status: retry 500",
]
for message in cases:
    print(f"{message!r}")
    print(f"  current={status_code_current(message)!r}")
    print(f"  proposed={status_code_proposed(message)!r}")
PY

Repository: NVIDIA/infra-controller

Length of output: 5847


Parse only the status token after HTTP .

HealthError::HttpError includes URLs and other free-form values. The current scan can report an IP octet such as 100 from https://10.0.0.100 instead of HTTP 500. Parse the 3-digit token immediately after HTTP and add a regression test for this input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/health/src/bmc.rs` around lines 790 - 802, Update
http_error_status_code to locate the “HTTP ” marker and parse only the
immediately following three-digit status token, avoiding unrelated numeric
values such as URL IP octets; preserve validation through http::StatusCode and
add a regression test covering a message containing https://10.0.0.100 alongside
HTTP 500.

Comment on lines +58 to +94
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BmcLatencyAttribute {
All,
HttpResponseStatusCode,
HttpRequestMethod,
HttpPath,
ServerAddress,
UrlScheme,
BmcVendor,
BmcModel,
}

impl BmcLatencyAttribute {
pub const ATTRIBUTES: [Self; 7] = [
Self::HttpResponseStatusCode,
Self::HttpRequestMethod,
Self::HttpPath,
Self::ServerAddress,
Self::UrlScheme,
Self::BmcVendor,
Self::BmcModel,
];

pub fn label_name(self) -> &'static str {
match self {
Self::All => "all",
Self::HttpResponseStatusCode => "http_response_status_code",
Self::HttpRequestMethod => "http_request_method",
Self::HttpPath => "http_path",
Self::ServerAddress => "server_address",
Self::UrlScheme => "url_scheme",
Self::BmcVendor => "bmc_vendor",
Self::BmcModel => "bmc_model",
}
}
}

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.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound the label cardinality before enabling this by default.

BmcLatencyAttribute::ServerAddress puts the BMC IP address directly into a Prometheus label. ATTRIBUTES includes it, and the All selector (the documented default in config.rs and config.example.toml) pulls it in automatically. Combined with HttpPath, HttpRequestMethod, HttpResponseStatusCode, BmcVendor, and BmcModel, every distinct BMC endpoint multiplies the number of histogram series. On a large fleet, this can produce a very high cardinality series set once an operator sets enable_bmc_latency_metrics = true without narrowing bmc_latency_attributes.

Exclude ServerAddress from the default attribute set, or document the cardinality cost prominently next to enable_bmc_latency_metrics in config.rs and config.example.toml so operators narrow the label set deliberately before enabling this in production.

As per coding guidelines: "Keep metric label cardinality bounded; use typed bounded labels and put machine IDs, IPs, and error text in context or logs rather than labels."

🤖 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/metrics.rs` around lines 58 - 94, Remove
BmcLatencyAttribute::ServerAddress from the default
BmcLatencyAttribute::ATTRIBUTES set so the All selector does not automatically
expose unbounded BMC IP addresses as Prometheus labels. Leave the enum and
label_name mapping available for explicit opt-in, while preserving the other
default attributes.

Source: Coding guidelines

Comment thread crates/health/src/metrics.rs
Comment on lines +127 to +134
let latency_ms = HistogramVec::new(
HistogramOpts::new(
format!("{prefix}_bmc_latency_ms"),
"Duration of outbound Redfish HTTP requests to BMCs, in milliseconds",
)
.buckets(bmc_latency_buckets_ms()),
&label_names,
)?;

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for a metrics catalogue file that documents framework/hardware-health metrics.
rg -n --iglob '*metric*catalog*' -l . 2>/dev/null
fd -i 'metric' -e md -e toml | xargs -I{} rg -n 'bmc_latency|carbide_hardware_health' {} 2>/dev/null

Repository: NVIDIA/infra-controller

Length of output: 169


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target file structure ---'
ast-grep outline crates/health/src/metrics.rs 2>/dev/null || true

printf '%s\n' '--- target implementation ---'
sed -n '1,220p' crates/health/src/metrics.rs

printf '%s\n' '--- metric references and registration ---'
rg -n -C 3 'bmc_latency|HistogramVec|metrics catalogue|metric catalogue|catalogue|Registry::register|register\(' crates .github 2>/dev/null | head -n 500

printf '%s\n' '--- candidate catalogue and integration-test files ---'
git ls-files | rg -i '(^|/)(metric|metrics|catalog|catalogue|test_integration|integration)' | head -n 300

Repository: NVIDIA/infra-controller

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- core metrics catalogue ---'
if [ -f docs/observability/core_metrics.md ]; then
  rg -n -C 3 'bmc|hardware_health|latency|component_failures|component_duration' docs/observability/core_metrics.md | head -n 300
  wc -l docs/observability/core_metrics.md
else
  printf '%s\n' 'catalogue file not found'
fi

printf '%s\n' '--- metric documentation tooling ---'
sed -n '1,260p' crates/xtask/src/metric_docs.rs
rg -n -C 5 'test_integration|core_metrics\.md|check-metric-docs|metrics scrape|/metrics' crates/xtask .github Makefile* Cargo.toml docs 2>/dev/null | head -n 500

printf '%s\n' '--- health metric construction and callers ---'
rg -n -C 5 'BmcLatencyMetrics::|enable_bmc_latency_metrics|MetricsConfig|create_.*registry|framework_registry' crates/health crates | head -n 500

Repository: NVIDIA/infra-controller

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- exact catalogue and health metric names ---'
rg -n 'carbide_hardware_health|bmc_latency_ms|bmc_latency_milliseconds|component_failures|component_duration' docs crates .github Makefile.toml 2>/dev/null | head -n 200

printf '%s\n' '--- integration catalogue generation implementation ---'
rg -n -C 8 'core_metrics\.md|test_integration|generate.*metric|metric.*catalog|scrape.*metrics|/metrics' --glob '*.rs' --glob '*.toml' --glob '*.md' . 2>/dev/null | rg -v 'target/' | head -n 500

printf '%s\n' '--- health tests and callers of BmcLatencyMetrics ---'
rg -n -C 8 'BmcLatencyMetrics|BmcLatencyObservation|bmc_latency' crates/health crates/*/tests crates/*/src 2>/dev/null | head -n 500

Repository: NVIDIA/infra-controller

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

metrics = Path("crates/health/src/metrics.rs").read_text()
config = Path("crates/health/src/config.rs").read_text()
catalogue = Path("docs/observability/core_metrics.md").read_text()
integration = Path("crates/api-integration-tests/tests/lib.rs").read_text()
health_bmc = Path("crates/health/src/bmc.rs").read_text()

name_expr = re.search(r'format!\("\{prefix\}_bmc_latency_ms"\)', metrics)
default_prefix = re.search(r'prefix:\s*"([^"]+)"\.to_string\(\)', config).group(1)
actual = f"{default_prefix}_bmc_latency_ms"
print(f"metric_name_expression_found={bool(name_expr)}")
print(f"default_prefix={default_prefix}")
print(f"default_metric_name={actual}")
print(f"catalogue_contains_default_metric={f'<td>{actual}</td>' in catalogue}")
print(f"catalogue_contains_canonical_candidate={f'<td>{default_prefix}_bmc_latency_milliseconds</td>' in catalogue}")
print(f"test_integration_scrapes_metrics={bool(re.search(r'generate_core_metric_docs\\(&test_env\\.carbide_metrics_addrs\\)', integration))}")
print(f"health_bmc_tests_assert_current_name={bool(re.search(r'test_health_bmc_latency_ms', health_bmc))}")
print(f"health_endpoint_in_integration_addresses={('health' in integration.lower() and 'metrics_addrs' in integration)}")
PY

printf '%s\n' '--- focused integration setup and health metric tests ---'
sed -n '260,330p' crates/api-integration-tests/tests/lib.rs
sed -n '2390,2470p' crates/health/src/bmc.rs
sed -n '400,425p' crates/health/example/config.example.toml

Repository: NVIDIA/infra-controller

Length of output: 546


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

metrics = Path("crates/health/src/metrics.rs").read_text()
config = Path("crates/health/src/config.rs").read_text()
catalogue = Path("docs/observability/core_metrics.md").read_text()
integration = Path("crates/api-integration-tests/tests/lib.rs").read_text()
health_bmc = Path("crates/health/src/bmc.rs").read_text()

name_expr = re.search(r'format!\("\{prefix\}_bmc_latency_ms"\)', metrics)
default_prefix = re.search(r'prefix:\s*"([^"]+)"\.to_string\(\)', config).group(1)
actual = f"{default_prefix}_bmc_latency_ms"
integration_scrape = bool(re.search(r'generate_core_metric_docs\(&test_env\.carbide_metrics_addrs\)', integration))
health_test_name = bool(re.search(r'test_health_bmc_latency_ms', health_bmc))
health_endpoint = 'health' in integration.lower() and 'metrics_addrs' in integration

print(f"metric_name_expression_found={bool(name_expr)}")
print(f"default_prefix={default_prefix}")
print(f"default_metric_name={actual}")
print(f"catalogue_contains_default_metric={f'<td>{actual}</td>' in catalogue}")
print(f"catalogue_contains_canonical_candidate={f'<td>{default_prefix}_bmc_latency_milliseconds</td>' in catalogue}")
print(f"test_integration_scrapes_metrics={integration_scrape}")
print(f"health_bmc_tests_assert_current_name={health_test_name}")
print(f"health_endpoint_in_integration_addresses={health_endpoint}")
PY

printf '%s\n' '--- focused integration catalogue generation ---'
sed -n '260,330p' crates/api-integration-tests/tests/lib.rs

printf '%s\n' '--- focused health metric tests ---'
sed -n '2390,2470p' crates/health/src/bmc.rs

printf '%s\n' '--- example configuration ---'
sed -n '400,425p' crates/health/example/config.example.toml

Repository: NVIDIA/infra-controller

Length of output: 6797


Use _milliseconds and add catalogue coverage.

Rename {prefix}_bmc_latency_ms to {prefix}_bmc_latency_milliseconds. Add the health endpoint to test_integration coverage so the generated catalogue includes the metric and its HELP text. Update the existing test and example configuration references.

🤖 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/metrics.rs` around lines 127 - 134, Rename the metric
constructed in the latency HistogramVec to use the
`{prefix}_bmc_latency_milliseconds` suffix, then update all existing test and
example configuration references to the new name. Extend `test_integration` to
exercise the health endpoint so the generated metric catalogue includes this
metric and its HELP text.

Source: Coding guidelines

@poroh

poroh commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Implementation thoughts: This implementation generates the metrics in the Bmc client wrapper. It thereby is limited to the info that the nvredfish exposes via its public interface and involves a fair amount of code changes.

Other approaches we can consider are:

  • Let nvredfish emit the metrics directly (but that binds it to a metrics framework)
  • Let nvredfish emit a tracing::Event with the data required (request type, destination, latency). And install a tracing::Subscriber in health which transforms it into a metric. That might however lack some data that nvredfish might not have (e.g. machine_id or rack_id) and that might be harder to retrieve from the subscriber context. But it would have less code throughout health.

@poroh @yoks Let me know if there's any preference

@Matthias247 nv-redfish by itself is transport-agnostic, you can replace BMC or HTTP client implementation, including to those that emits any metrics you want. To do this you can just create HTTP client by implementing the trait:

https://github.com/NVIDIA/nv-redfish/blob/18d52581407d346a4eaa57b85786505ff6fd9311/bmc-http/src/lib.rs#L93

And use it here:

pub type RedfishBmc = HttpBmc<RedfishReqwestClient>;

Instead of using nv_redfish::bmc_http::reqwest::Client directly.

@Matthias247

Copy link
Copy Markdown
Contributor Author

@Matthias247 nv-redfish by itself is transport-agnostic, you can replace BMC or HTTP client implementation, including to those that emits any metrics you want. To do this you can just create HTTP client by implementing the trait:

https://github.com/NVIDIA/nv-redfish/blob/18d52581407d346a4eaa57b85786505ff6fd9311/bmc-http/src/lib.rs#L93

And use it here:

pub type RedfishBmc = HttpBmc<RedfishReqwestClient>;

Instead of using nv_redfish::bmc_http::reqwest::Client directly.

That sounds like a good path! I'll try it

@Matthias247

Matthias247 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@poroh I changed to that implementation in the second commit by adding InstrumntedHttpClient. The way its implemented right now (by wrapping the nvredfish client) it unfortunately does not resolve the limitation of not observing the status code directly for 2xx codes.

This would either require rebuilding most of the client, or changing nv-redfish. I'd probably lean to the latter. And either

  1. directly add the capability to emit metrics to it
  2. add a hook like dyn Fn(HttpExecutionTiming) that is called after each response
  3. add a &mut Timing parameter to the client invocation which is then filled with details by the implementation
  4. Change the response type to some aggregate type which includes timing
  5. emit a tracing::Event that one can subscribe to

Options 2 to 4 all require changes to function signatures, and I'm not sure if the use-case is significant enough for other applications to justify.

Option 5 is somewhat elegant - but adding custom attributes to the metric that are not visible to nvredfish is probably trickier. E.g. also showing machine_id or bmc_vendor as metric attribute - which are both not directly available inside the http call. Could probably be worked around by setting these as some attributes on the tracing span or storing them in a custom subscriber extension.

But I personally can also live with the inaccuracy of the 2xx codes. It's probably more the question on what is most useful for other services which want to have the same metric.

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

Issues aren't really blocking, feel free to ignore.

I think at this point if we're going to default to per-machine/per-BMC metrics like we are, we may want to change the guidance in STYLE_GUIDE.md to no longer say that per-machine metrics are discouraged. I think the people using the health service find per-machine metrics useful enough that maybe we just declare that "the number of machines in an environment" no longer constitutes "high-cardinality"? Something to think about at least.

Arc::new(FailingProvider),
None,
10,
None,

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.

Getting pretty close to needing a proper BmcClientArgs struct or something here, so that we can at least see name: value pairs... seeing 6 arguments with no names gets super hard to read after a while.

Comment thread crates/health/src/metrics.rs Outdated
@Matthias247

Copy link
Copy Markdown
Contributor Author

@poroh @kensimon Pushed one more change which adds the additional labels machine_id, rack_id and entity_type. The latter is derived from the Rust type name.

I mostly see the use-case here as being able to differentiate whether the BMC implementation is slow for polling sensors or for other requests.

@Matthias247

Copy link
Copy Markdown
Contributor Author

I think at this point if we're going to default to per-machine/per-BMC metrics like we are, we may want to change the guidance in STYLE_GUIDE.md to no longer say that per-machine metrics are discouraged.

I still think its a good best practice. In this case I made the set of labels configurable so that deployers can choose whether to have per-machine metrics or whether to roll up by certain parameters.

@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/health/src/api_client.rs (1)

605-627: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Cached clients keep the endpoint labels captured at first construction.

cache_or_create_bmc_client invokes the factory only on a cache miss, and the cache is keyed by MAC address. On a cache hit the bmc_latency_instrumentation computed at line 605 is dropped, and the cached BmcClient continues to use the machine_id and rack_id values captured when it was first created.

endpoint_for recomputes metadata and rack_id from a fresh API response on every discovery cycle, so those values can legitimately change for a stable MAC. Two reachable cases:

  • A machine is relocated and machine.rack_id changes. The histogram keeps publishing the previous rack_id.
  • machine.id transitions from absent to present after registration. The histogram keeps publishing machine_id="unknown".

prune_bmc_client_cache only evicts MACs that disappeared, so neither case ever refreshes the labels. The result is silently incorrect identity dimensions with no error and no failing test. Static endpoints in endpoint/sources.rs are unaffected because they build a fresh client per configuration load.

Consider holding the endpoint labels behind a shared cell that endpoint_for updates on every cycle, so the cached client observes the current identity.

#!/bin/bash
# Description: Confirm that cache_or_create_bmc_client skips the factory on a cache hit
# and that nothing else refreshes a cached client's latency labels.
set -eu

printf '%s\n' '--- cache_or_create_bmc_client definition ---'
ast-grep run --pattern 'fn cache_or_create_bmc_client($$$) { $$$ }' --lang rust crates/health

printf '%s\n' '--- fallback text search if the pattern did not match ---'
rg -n -C 20 'fn cache_or_create_bmc_client' crates/health

printf '%s\n' '--- all call sites and cache eviction logic ---'
rg -n -C 6 'cache_or_create_bmc_client|bmc_client_cache' crates/health
🤖 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/api_client.rs` around lines 605 - 627, Update the cached
BMC client flow around BmcLatencyInstrumentation::new and endpoint_for so
endpoint identity labels are stored in shared mutable state rather than captured
only during BmcClient construction. Refresh that state with the current
machine_id and rack_id on every discovery cycle, and have the cached client read
the latest values when publishing latency metrics while preserving cache reuse
by MAC address.
🧹 Nitpick comments (8)
crates/health/src/config.rs (3)

1700-1702: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the precedence of the all sentinel.

When the list contains all, this function discards every other entry and returns the complete attribute set. An operator writing ["http_path", "all"] receives all ten labels, not one. The behaviour is reasonable, and the test at line 3196 pins it, but the field doc comment at line 1641 does not state it. Add a short note there so the resolution rule is discoverable from the configuration contract.

As per coding guidelines: "Define and document create, update, patch, replacement, field-mask, clear, preserve, precedence, fallback, validation, and explicit-default semantics".

📝 Proposed documentation change
-    /// Label attributes emitted on the BMC latency histogram.
+    /// Label attributes emitted on the BMC latency histogram.
+    ///
+    /// Accepts the snake_case label names plus the `all` sentinel. If the list
+    /// contains `all`, every supported attribute is selected and the remaining
+    /// entries are ignored. Unknown names are rejected at load time. The
+    /// resolved order always follows `BmcLatencyAttribute::ATTRIBUTES`.
     #[serde(
🤖 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 1700 - 1702, Update the field
documentation near the configuration parsing logic to state that including the
`all` sentinel takes precedence over every other listed attribute and selects
the complete attribute set. Keep the existing `has_all` behavior in the
surrounding function unchanged.

Source: Coding guidelines


3169-3215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a rejection case for an unknown label.

The parser has a dedicated branch that collects unrecognised labels and reports them with the allowed-values list. No test reaches it. That branch is the only validation feedback an operator receives for a typo such as machineid, and a silent regression there would turn a misspelled label into a hard-to-diagnose missing dimension.

Add a case that parses an invalid label and asserts the extraction fails.

As per coding guidelines: "Prefer table-driven tests for functions mapping inputs to outputs or errors."

💚 Proposed test addition
#[test]
fn bmc_latency_attributes_reject_unknown_labels() {
    let toml_content = r#"
[metrics]
bmc_latency_attributes = ["http_path", "machineid"]
"#;

    let error = Figment::new()
        .merge(Serialized::defaults(Config::default()))
        .merge(Toml::string(toml_content))
        .extract::<Config>()
        .expect_err("unknown BMC latency attribute must be rejected");

    let message = error.to_string();
    assert!(message.contains("machineid"), "unexpected error: {message}");
    assert!(message.contains("machine_id"), "unexpected error: {message}");
}
🤖 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 3169 - 3215, Add a table-driven
rejection test near bmc_latency_metrics_config_parsing that feeds unknown BMC
latency labels such as “machineid” through Config extraction, asserts extraction
fails, and verifies the error includes both the invalid label and an allowed
label such as “machine_id”.

Source: Coding guidelines


1722-1729: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the accessor so it cannot be confused with the field.

MetricsConfig exposes both the public field bmc_latency_attributes and the method bmc_latency_attributes(). They differ only by parentheses and return different values whenever the field holds a non-canonical or duplicated list, which is reachable because the field is public and Config::default() bypasses the custom deserializer. The assertions at lines 3149 to 3156 check both, which shows how close the two names sit. A caller that reads the field directly silently skips canonicalisation.

Rename the method to resolved_bmc_latency_attributes and update the call sites in validate and in the wiring layer.

As per coding guidelines: "design APIs that are hard to misuse".

♻️ Proposed rename
 impl MetricsConfig {
-    pub fn bmc_latency_attributes(&self) -> Vec<BmcLatencyAttribute> {
+    pub fn resolved_bmc_latency_attributes(&self) -> Vec<BmcLatencyAttribute> {
         BmcLatencyAttribute::ATTRIBUTES
             .iter()
             .copied()
             .filter(|attribute| self.bmc_latency_attributes.contains(attribute))
             .collect()
     }
 
     fn validate(&self) -> Result<(), String> {
-        if self.enable_bmc_latency_metrics && self.bmc_latency_attributes().is_empty() {
+        if self.enable_bmc_latency_metrics && self.resolved_bmc_latency_attributes().is_empty() {
🤖 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 1722 - 1729, Rename the
MetricsConfig accessor bmc_latency_attributes() to
resolved_bmc_latency_attributes() to distinguish it from the public field,
preserving its canonicalizing behavior. Update every call site, specifically the
validate logic and wiring layer, to use the renamed method while leaving direct
field access unchanged.

Source: Coding guidelines

crates/health/src/bmc.rs (4)

270-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The Any downcast is contained, but consider a narrower hook.

note_bmc_identity_from inspects the runtime type of every decoded entity to find ServiceRoot. The cost is one TypeId comparison per successful read, which is negligible, and the function is small and clearly named. The trade-off is that the identity capture is invisible from the ServiceRoot type itself: a future rename or a second identity-bearing schema type will not produce a compile error here, only a silent loss of the bmc_vendor and bmc_model labels.

If a follow-up adds more identity sources, prefer a small local trait with an explicit implementation over widening this downcast chain.

As per coding guidelines: "Prefer simple, explicit, readable, maintainable Rust over cleverness or unnecessary abstraction".

🤖 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/bmc.rs` around lines 270 - 279, Keep note_bmc_identity_from
focused on the existing ServiceRoot identity source; no immediate change is
required. If adding additional identity-bearing schema types later, replace the
expanding Any downcast chain with a small local trait and explicit
implementations for each supported type, preserving the existing vendor and
product label capture.

Source: Coding guidelines


224-243: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider a parameter struct for the six-argument constructor.

BmcClient::new now takes six positional parameters, two of which are Option values with unrelated meanings: proxy_url and bmc_latency_instrumentation. Call sites read BmcClient::new(reqwest(), test_addr(), provider, None, 10, None). Transposing the two None arguments compiles without error and silently disables either the proxy or the instrumentation. The bare 10 for cache_size has the same weakness.

A BmcClientParams struct built with a struct literal would make each value self-describing at every call site and would absorb future optional parameters without another signature change. The change touches roughly twenty-five call sites in this file, so it is a deliberate follow-up rather than a blocker.

As per coding guidelines: "design APIs that are hard to misuse" and "builders only when they improve large optional initialization without weakening required-field checks".

🤖 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/bmc.rs` around lines 224 - 243, Introduce a BmcClientParams
parameter struct containing the current BmcClient::new arguments, including
explicitly named proxy_url, cache_size, and bmc_latency_instrumentation fields;
change BmcClient::new to accept this struct while keeping required dependencies
validated through the struct literal. Update all call sites in the file to
construct BmcClientParams with named fields, preserving each existing value and
behavior.

Source: Coding guidelines


796-814: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Record the estimated-success-code caveat in the code.

The success status codes are estimates. get and sse report 200, post_session reports 201, and post selects between 200 and 201 from the URL path. The underlying client does not expose the observed 2xx code, so a 204 or 206 response is published under http_response_status_code="200". Error codes are accurate.

The pull request description explains this, but an operator reading a dashboard will see only the label. Add a short doc comment on the trait implementation so the constraint travels with the code.

📝 Proposed documentation change
+/// Times every Redfish request and records the result on the BMC latency
+/// histogram.
+///
+/// The underlying client does not surface the observed 2xx status code, so the
+/// success codes below are estimates: reads report `200` and creations report
+/// `201`. Task and empty responses are distinguished, and error status codes
+/// are taken from the transport error and are exact.
 impl HttpClient for InstrumentedHttpClient {
     type Error = BmcError;
🤖 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/bmc.rs` around lines 796 - 814, Add a short documentation
comment on the trait implementation containing get, sse, post_session, and post
to state that success status codes are estimated because the underlying client
does not expose the observed 2xx code, while error codes remain accurate;
mention the current reported values and post’s URL-based 200/201 selection.

2679-2715: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit coverage for the pure status-selection helpers.

The three metric tests exercise the wire path well, including the 503 error case and the restricted attribute set. Two new branches remain uncovered:

  • post_entity_status_code returns 200 for a path containing /Actions/ and 201 otherwise. No test reaches either arm. This branch encodes a Redfish URI convention and would regress silently.
  • bmc_error_status_code returns None for every error variant other than InvalidResponse, so transport failures fall back to the unknown bucket. No test pins that fallback.

Both are pure functions, so table-driven tests need no scripted server.

As per coding guidelines: "Prefer table-driven tests for functions mapping inputs to outputs or errors. Use scenarios! with Outcome for Result operations, value_scenarios! for plain values".

💚 Proposed test addition
#[test]
fn post_entity_status_code_distinguishes_actions_from_creation() {
    value_scenarios!(run = |path: &str| {
        let url = Url::parse(&format!("https://10.0.0.1{path}")).expect("valid url");
        post_entity_status_code(&url)
    };
        "action invocations report 200" {
            "/redfish/v1/Systems/1/Actions/ComputerSystem.Reset" => "200",
        }

        "resource creation reports 201" {
            "/redfish/v1/SessionService/Sessions" => "201",
        }
    );
}

#[test]
fn transport_errors_fall_back_to_the_unknown_status_bucket() {
    let result: Result<(), BmcError> = Err(bmc_status_error(http::StatusCode::SERVICE_UNAVAILABLE));
    assert_eq!(result_status_code(&result, "200"), "503");

    let decode_failure: Result<(), BmcError> = Err(BmcError::InvalidResponse {
        url: Url::parse("https://10.0.0.1/redfish/v1").expect("valid url"),
        status: http::StatusCode::BAD_GATEWAY,
        text: String::new(),
    });
    assert_eq!(result_status_code(&decode_failure, "200"), "502");
}
🤖 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/bmc.rs` around lines 2679 - 2715, Add table-driven unit
tests for the pure status-selection helpers: cover both `/Actions/` and
non-action paths in `post_entity_status_code`, and verify
`bmc_error_status_code`/`result_status_code` maps non-`InvalidResponse`
transport errors to the unknown bucket while preserving the HTTP status for
`InvalidResponse`. Use the project’s `value_scenarios!` or `scenarios!`
conventions without adding scripted-server setup.

Source: Coding guidelines

crates/health/src/metrics.rs (1)

87-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider Display and FromStr instead of the bespoke label conversions.

label_name and from_label_name implement a string mapping that the standard traits already model. Display plus FromStr would let config.rs parse through str::parse and would compose with serde helpers. The current linear scan in from_label_name is acceptable at ten variants and only runs at configuration load.

As per coding guidelines: "Model finite possibilities with enums or typed structures implementing Display and FromStr".

🤖 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/metrics.rs` around lines 87 - 107, The metric attribute
enum’s bespoke label conversion methods should use standard string-conversion
traits instead. Implement Display for the enum using the existing label
mappings, implement FromStr with the current matching behavior and appropriate
parse error type, then update callers such as config parsing to use str::parse
and adjust serde integration to reuse these traits; remove or replace label_name
and from_label_name while preserving all mappings.

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/bmc.rs`:
- Around line 1025-1032: Replace the compiler-derived entity_type_name<T>
implementation with stable Redfish identifiers, using an explicit
type-to-identifier mapping for the supported entity types. Update callers to use
that mapping and ensure metric labels remain unchanged across Rust toolchain
upgrades.

---

Outside diff comments:
In `@crates/health/src/api_client.rs`:
- Around line 605-627: Update the cached BMC client flow around
BmcLatencyInstrumentation::new and endpoint_for so endpoint identity labels are
stored in shared mutable state rather than captured only during BmcClient
construction. Refresh that state with the current machine_id and rack_id on
every discovery cycle, and have the cached client read the latest values when
publishing latency metrics while preserving cache reuse by MAC address.

---

Nitpick comments:
In `@crates/health/src/bmc.rs`:
- Around line 270-279: Keep note_bmc_identity_from focused on the existing
ServiceRoot identity source; no immediate change is required. If adding
additional identity-bearing schema types later, replace the expanding Any
downcast chain with a small local trait and explicit implementations for each
supported type, preserving the existing vendor and product label capture.
- Around line 224-243: Introduce a BmcClientParams parameter struct containing
the current BmcClient::new arguments, including explicitly named proxy_url,
cache_size, and bmc_latency_instrumentation fields; change BmcClient::new to
accept this struct while keeping required dependencies validated through the
struct literal. Update all call sites in the file to construct BmcClientParams
with named fields, preserving each existing value and behavior.
- Around line 796-814: Add a short documentation comment on the trait
implementation containing get, sse, post_session, and post to state that success
status codes are estimated because the underlying client does not expose the
observed 2xx code, while error codes remain accurate; mention the current
reported values and post’s URL-based 200/201 selection.
- Around line 2679-2715: Add table-driven unit tests for the pure
status-selection helpers: cover both `/Actions/` and non-action paths in
`post_entity_status_code`, and verify
`bmc_error_status_code`/`result_status_code` maps non-`InvalidResponse`
transport errors to the unknown bucket while preserving the HTTP status for
`InvalidResponse`. Use the project’s `value_scenarios!` or `scenarios!`
conventions without adding scripted-server setup.

In `@crates/health/src/config.rs`:
- Around line 1700-1702: Update the field documentation near the configuration
parsing logic to state that including the `all` sentinel takes precedence over
every other listed attribute and selects the complete attribute set. Keep the
existing `has_all` behavior in the surrounding function unchanged.
- Around line 3169-3215: Add a table-driven rejection test near
bmc_latency_metrics_config_parsing that feeds unknown BMC latency labels such as
“machineid” through Config extraction, asserts extraction fails, and verifies
the error includes both the invalid label and an allowed label such as
“machine_id”.
- Around line 1722-1729: Rename the MetricsConfig accessor
bmc_latency_attributes() to resolved_bmc_latency_attributes() to distinguish it
from the public field, preserving its canonicalizing behavior. Update every call
site, specifically the validate logic and wiring layer, to use the renamed
method while leaving direct field access unchanged.

In `@crates/health/src/metrics.rs`:
- Around line 87-107: The metric attribute enum’s bespoke label conversion
methods should use standard string-conversion traits instead. Implement Display
for the enum using the existing label mappings, implement FromStr with the
current matching behavior and appropriate parse error type, then update callers
such as config parsing to use str::parse and adjust serde integration to reuse
these traits; remove or replace label_name and from_label_name while preserving
all mappings.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: abd12f8e-6dcb-4cba-a157-3cb3a28198cc

📥 Commits

Reviewing files that changed from the base of the PR and between 67bf8a8 and 3c1c167.

📒 Files selected for processing (7)
  • crates/health/example/config.example.toml
  • crates/health/src/api_client.rs
  • crates/health/src/bmc.rs
  • crates/health/src/config.rs
  • crates/health/src/endpoint/cluster.rs
  • crates/health/src/endpoint/sources.rs
  • crates/health/src/metrics.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/health/example/config.example.toml
  • crates/health/src/endpoint/cluster.rs

Comment thread crates/health/src/bmc.rs
Comment on lines +1025 to +1032
fn entity_type_name<T>() -> &'static str {
let type_name = type_name::<T>();
let without_generics = type_name.split('<').next().unwrap_or(type_name);
without_generics
.rsplit("::")
.next()
.unwrap_or(without_generics)
}

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the Rust standard library guarantee stability of std::any::type_name output across compiler versions?

💡 Result:

No, the Rust standard library does not guarantee the stability of the output from std::any::type_name across compiler versions [1][2]. The official documentation explicitly states that the exact contents and format of the string returned are not specified, serving only as a best-effort description of the type [1][3]. Because the implementation is intended strictly for diagnostic purposes, the output is subject to change between different versions of the compiler [1][4]. For example, the inclusion of certain details, such as lifetime specifiers, has changed in past compiler releases [1][5]. Furthermore, the documentation warns against using the output of std::any::type_name as a unique identifier for a type or to affect program behavior, as it may not be consistent or unique [1][3]. Users are encouraged to rely on it only for debugging and logging [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
file=$(git ls-files | rg '^crates/health/src/bmc\.rs$')
wc -l "$file"
sed -n '1,140p' "$file"
sed -n '980,1065p' "$file"
rg -n -C 4 'entity_type_name|entity_type|odata\.type|OData|type_name' "$file" crates/health

Repository: NVIDIA/infra-controller

Length of output: 1935


🌐 Web query:

site:github.com/NVIDIA/infra-controller crates/health/src/bmc.rs entity_type_name

💡 Result:

In the context of the NVIDIA infra-controller repository, crates/health/src/bmc.rs is a source file associated with the health monitoring subsystem [1][2]. The identifier entity_type_name is commonly used within this codebase as a trait method or field associated with BMC (Baseboard Management Controller) and other hardware entities to provide a human-readable or system-identifiable name for the entity type (e.g., distinguishing between different hardware classes like Switches, DPUs, or Servers) [1][3]. While the exact implementation of entity_type_name in crates/health/src/bmc.rs may evolve with the codebase, it is part of the broader logic used for hardware discovery, BMC credential management, and Redfish-based monitoring [1][4]. This identifier facilitates the categorization of hardware nodes within the controller, allowing the system to apply appropriate collectors (e.g., leak detectors, power state management, or firmware inventory) based on the specific hardware type identified [1][2][5].

Citations:


Use a stable Redfish identifier for the entity_type label.

std::any::type_name does not guarantee stable output across compiler versions. Deriving a published metric label from it can break dashboards and recording rules after a toolchain upgrade. Use a stable Redfish identifier or an explicit mapping instead.

🤖 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/bmc.rs` around lines 1025 - 1032, Replace the
compiler-derived entity_type_name<T> implementation with stable Redfish
identifiers, using an explicit type-to-identifier mapping for the supported
entity types. Update callers to use that mapping and ensure metric labels remain
unchanged across Rust toolchain upgrades.

Adds new config file flags to hw-health which make it emit a histogram metric for requests to BMCs.
The metric with all properties attached will look like:
```
carbide_hardware_health_bmc_latency_ms_bucket{http_response_status_code="200",http_request_method="GET",http_path="/redfish/
  v1",server_address="1.3.5.12",url_scheme="https",bmc_vendor="NVIDIA",bmc_model="GB200 BMC",le="100"} 1
```

The metric can be explicitly enabled via setting
```toml
[metrics]
enable_bmc_latency_metrics = true
```

By default it will contain all available fields. If only a subset of fields should be emitted to reduce the amount of time series, the setting `metrics.bmc_latency_attributes` can be used to explicitly specify the attributes that should be included. Adding `all` to the list leads to emitting all attributes (default) again.

```toml
[metrics]
bmc_latency_attributes = ["http_response_status_code", "server_address", "url_scheme"]
```

Limitation:

The status code for success responses are estimated since nv-redfish does not expose it. It is always set to 200 for GET requests and 201 for creation requests. Status codes for error responses are accurate.
@github-actions

Copy link
Copy Markdown

Wraps the nvredfish HttpClient and emits metrics in it.

Signed-off-by: Matthias Einwag <meinwag@nvidia.com>
@Matthias247
Matthias247 merged commit bfcfdeb into NVIDIA:main Aug 11, 2026
65 checks passed
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.

5 participants