Skip to content

Add metrics for config-load failures and startup latency (#303) - #379

Open
leynos wants to merge 6 commits into
mainfrom
issue-303-config-load-metrics
Open

Add metrics for config-load failures and startup latency (#303)#379
leynos wants to merge 6 commits into
mainfrom
issue-303-config-load-metrics

Conversation

@leynos

@leynos leynos commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #303

Adds the metrics instrumentation requested as a follow-up to PR #297: a config-load failure counter and a startup-latency histogram, plus developer documentation.

Changes

  • Cargo.toml: add the metrics façade (runtime) and metrics-util (dev, debugging feature).
  • src/main.rs: introduce resolve_configuration (spans cli::resolve_merged_diag_json through cli::merge_with_config) and record_config_load_metrics, emitting:
    • netsuke_config_load_total — counter labelled outcome (success/failure);
    • netsuke_config_load_duration_seconds — duration histogram.
      The merge error path is extracted into handle_config_load_error. Because metrics is a façade, the instruments are no-ops until an operator installs a recorder; Netsuke bundles none.
  • docs/developers-guide.md: new Configuration-load observability subsection documenting counter names, label conventions, and suggested histogram buckets.

Testing

  • Unit tests use metrics_util::debugging::DebuggingRecorder + metrics::with_local_recorder to assert the counter carries outcome=failure/outcome=success and that the histogram records exactly one sample.

Structured log fields (operation, error_category) and per-phase counter labels are the scope of the follow-up #304.

Validation

  • make check-fmt / make markdownlint / make lint / make test — pass (37 suites)

🤖 Generated with Claude Code

Summary by Sourcery

Instrument startup configuration loading with metrics and document their usage.

New Features:

  • Add metrics-based instrumentation for configuration-load outcomes and durations during startup.

Enhancements:

  • Refactor configuration resolution into a dedicated function and centralised error handler to support metrics collection.

Build:

  • Add metrics and metrics-util crates to support runtime instrumentation and test-time inspection of metrics.

Documentation:

  • Document configuration-load observability, including metric names, semantics, and naming conventions in the developer guide.

Tests:

  • Add tests using a debugging metrics recorder to verify emitted configuration-load counters, labels, and histograms.

References

@coderabbitai

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

Add configuration-load observability for startup configuration resolution.

  • Record netsuke_config_load_total with success and failure outcomes.
  • Record netsuke_config_load_duration_seconds for configuration resolution and merging.
  • Centralize error handling to capture early failures.
  • Add metrics and metrics-util dependencies.
  • Document metric names, labels, recorder behaviour, and suggested histogram buckets.
  • Add tests for success and failure metrics using a debugging recorder.

Addresses issue #303. Structured log fields and per-phase labels remain deferred to issue #304.

Walkthrough

Startup configuration resolution and merging now record success or failure outcomes and elapsed duration. Tests verify the emitted metrics. The developer guide documents metric names, labels, buckets, and recorder behaviour.

Changes

Configuration-load observability

Layer / File(s) Summary
Metric instrumentation
src/main.rs
Start timing during configuration loading. Record early failures, completed failures, and successful loads through the metrics façade.
Metric capture and validation
src/main_tests.rs
Capture local metric snapshots. Verify outcome labels, one counter increment, and one duration histogram sample.
Observability documentation
docs/developers-guide.md
Document metric names, labels, recorder behaviour, duration buckets, and bounded cardinality.

Sequence Diagram(s)

sequenceDiagram
  participant Startup as Startup
  participant Config as Configuration loading
  participant Metrics as Metrics façade
  Startup->>Config: Resolve and merge configuration
  Config-->>Startup: Return success or early failure
  Startup->>Metrics: Record outcome counter
  Startup->>Metrics: Record elapsed duration histogram
Loading

Possibly related PRs

  • leynos/netsuke#329: Adds tracing for configuration path discovery and explicit-load failures alongside this configuration instrumentation.

Suggested labels: Issue

Suggested reviewers: codescene-access

Poem

Startup clocks begin to chime,
Success and failure mark the time.
Histograms catch each loading beat,
Tests make every count complete.
Docs chart the metrics’ flight.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 2 warnings, 5 inconclusive)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error The only new metrics test calls record_config_load_metrics directly; no test invokes run_with_args, so removed or misplaced production call sites would still pass, and the histogram assertion c... Add recorder-backed tests through run_with_args or an extracted orchestration boundary for success, merge failure, and JSON-resolution failure; assert exact metric names, labels, count, and duration value.
Module-Level Documentation ❌ Error src/main_tests.rs documents only startup diagnostics and gating, but now also provides recorder helpers and configuration-load metric tests; the module purpose is incomplete. Update the module-level docs in src/main_tests.rs to cover metric capture and its relationship to the startup entry point; extend src/main.rs docs if needed.
Testing (Unit And Behavioural) ⚠️ Warning The added test calls private record_config_load_metrics directly; no test drives run_with_args through success, JSON-resolution failure, and merge-failure paths. Add behavioural tests that run the startup boundary under with_local_recorder and assert one labelled counter and one histogram for each success and failure path.
Testing (Property / Proof) ⚠️ Warning Recommend property testing: run_with_args spans success, JSON-resolution failure, and merge failure, yet tests cover only the helper with two boolean cases. Add a substantive proptest or bounded model test over startup outcomes and durations. Assert one bounded outcome counter and one histogram sample for every generated attempt, including early exits.
User-Facing Documentation ❓ Inconclusive Investigation in progress; no verdict yet. Inspect the committed documentation and implementation to verify whether user-facing behaviour is covered in docs/users-guide.md.
Unit Architecture ❓ Inconclusive I am still checking the startup boundary, clock dependency, and error paths before assigning the architecture verdict. Inspect the complete diff and related CLI APIs to confirm whether the new side-effects and fallibility remain explicit.
Performance And Resource Use ❓ Inconclusive Investigation in progress. Inspect the configuration-resolution paths and the exact patch before deciding.
Concurrency And State ❓ Inconclusive I am still checking whether the metrics façade introduces shared recorder state that needs explicit concurrency guarantees. Inspect the recorder API and startup ownership before deciding.
Architectural Complexity And Maintainability ❓ Inconclusive Initial repository inspection returned no pull request diff; source and dependency evidence is required before assessing architectural complexity. Inspect the checked-out source, dependency declarations, and tests for the described instrumentation.
✅ Passed checks (11 passed)
Check name Status Explanation
Title check ✅ Passed Accept the title because it describes the metrics change and includes the linked issue reference (#303).
Description check ✅ Passed Accept the description because it clearly explains the metrics, documentation, dependencies, tests, and validation for this changeset.
Linked Issues check ✅ Passed Accept the changes because they implement issue #303 with outcome counters, startup-duration histograms, documentation, and tests.
Out of Scope Changes check ✅ Passed Accept the scope because the dependencies, refactoring, documentation, and tests directly support issue #303.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Developer Documentation ✅ Passed Accept this check: docs/developers-guide.md documents both metric names, labels, timing scope, recorder behaviour, buckets, and naming rules; no roadmap or locale update is required.
Testing (Compile-Time / Ui) ✅ Passed The change adds runtime metrics, not compile-time or UI behaviour. Tests assert metric names, labels, counts, and samples with focused semantic checks; snapshots would add no value.
Domain Architecture ✅ Passed Metrics stay in the binary composition boundary (src/main.rs); the patch leaves netsuke domain modules and configuration APIs unchanged, with tests confined to main_tests.rs.
Observability ✅ Passed Startup resolution records bounded success/failure counters and duration histograms on success and both failure exits; documentation defines names, buckets, labels, and recorder behaviour.
Security And Privacy ✅ Passed The new metrics emit only fixed names, bounded success/failure labels, and a duration. No secrets, user data, credentials, permissions, or new trust-boundary sinks appear in the change.
Rust Compiler Lint Integrity ✅ Passed Keep the change: the commit adds no broad lint suppression or clone, and all new constants, imports, helpers, and metrics calls have verified references.
📋 Issue Planner

Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).

View plan for ticket: #303

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-303-config-load-metrics

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

@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds config-load observability by instrumenting startup configuration resolution with metrics, refactoring error handling, and documenting the new metrics, along with tests using a debugging recorder.

Sequence diagram for configuration-load metrics and error handling

sequenceDiagram
    participant main
    participant resolve_configuration
    participant cli as cli_merge
    participant metrics_facade
    participant handle_config_load_error

    main->>resolve_configuration: resolve_configuration(parsed_cli, matches)
    resolve_configuration->>cli: cli::resolve_merged_diag_json(parsed_cli, matches)
    resolve_configuration-->>resolve_configuration: DiagMode::from_json_enabled(...)
    resolve_configuration->>cli: cli::merge_with_config(parsed_cli, matches)
    resolve_configuration-->>metrics_facade: record_config_load_metrics(elapsed, merged.is_ok())
    metrics_facade-->>metrics_facade: metrics::histogram!(CONFIG_LOAD_DURATION_SECONDS)
    metrics_facade-->>metrics_facade: metrics::counter!(CONFIG_LOAD_TOTAL)
    resolve_configuration-->>main: (mode, merged)

    alt [merge succeeded]
        main-->>main: merged.with_default_command()
        main-->>main: configure_runtime(...)
    else [merge failed]
        main->>handle_config_load_error: handle_config_load_error(err, mode)
        handle_config_load_error-->>main: ExitCode::FAILURE
    end
Loading

File-Level Changes

Change Details Files
Instrument configuration-load phase with metrics and refactor startup configuration resolution and error handling.
  • Introduce resolve_configuration to compute diagnostic mode, perform config merge, and time the combined config-load phase.
  • Add CONFIG_LOAD_TOTAL counter and CONFIG_LOAD_DURATION_SECONDS histogram, and implement record_config_load_metrics to emit them via the metrics facade.
  • Extract handle_config_load_error to centralize config-load failure rendering and exit-code mapping, reusing prior JSON vs human-path behavior.
  • Update run_with_args to use resolve_configuration and handle_config_load_error, calling with_default_command only on successful merges.
  • Add unit tests validating counter labeling for success/failure and that exactly one histogram sample is recorded per invocation.
src/main.rs
Document configuration-load observability and metric conventions for operators and developers.
  • Add a Configuration-load observability subsection describing where instrumentation lives and how it behaves with the metrics facade.
  • Document the two emitted instruments, their semantics, and suggested histogram bucket boundaries.
  • Clarify metric naming and label cardinality conventions to guide future metrics additions.
docs/developers-guide.md
Wire in metrics dependencies for runtime use and test-time debugging.
  • Add the metrics crate as a runtime dependency for metrics facade macros.
  • Add metrics-util with debugging feature as a dev-dependency to support DebuggingRecorder-based tests.
  • Update Cargo.lock to capture the new dependency graph.
Cargo.toml
Cargo.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#303 Instrument the config-load error handling paths (including handle_config_load_error / resolve_diag_mode_or_exit / merge_cli_or_exit equivalents) with a counter labelled by outcome (success/failure) to track configuration-load failure rates.
#303 Wrap the startup configuration-resolution phase, from cli::resolve_merged_diag_json through cli::merge_with_config, in a duration histogram to record startup latency.
#303 Add developer documentation to docs/developers-guide.md describing the configuration observability instrumentation, including counter names, label conventions, and histogram buckets.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Configuration-load errors surface explicitly via `Result` boundaries
and structured logging, but no metrics existed to detect production
trends (failure rates, startup latency).

Introduce a config-load observability boundary in `src/main.rs`:
`resolve_configuration` spans diagnostic-mode resolution through the
layer merge and calls `record_config_load_metrics`, which emits a
`netsuke_config_load_total` counter labelled by `outcome`
(`success`/`failure`) and a `netsuke_config_load_duration_seconds`
histogram. The merge error path is extracted into
`handle_config_load_error`.

Recording goes through the `metrics` façade, so the instruments are
no-ops unless an operator installs a recorder; Netsuke emits the
measurements without bundling an exporter.

Document the counter names, label conventions, and histogram buckets
in `docs/developers-guide.md`. Add unit tests (using `metrics-util`'s
debugging recorder) asserting the counter's outcome label and the
single duration sample for both success and failure.
@lodyai
lodyai Bot force-pushed the issue-303-config-load-metrics branch from 400b733 to 700cdd8 Compare August 9, 2026 01:22
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review August 9, 2026 02:32

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai coderabbitai Bot added the Issue label Aug 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 700cdd8e74

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main.rs
Comment on lines +279 to +280
metrics::histogram!(CONFIG_LOAD_DURATION_SECONDS).record(elapsed.as_secs_f64());
metrics::counter!(CONFIG_LOAD_TOTAL, "outcome" => outcome).increment(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Install a recorder before emitting startup metrics

For every normal invocation of the standalone netsuke binary, these calls are discarded by the metrics façade because neither main nor any other production path installs a recorder (confirmed by a repository-wide search for recorder installation). An operator cannot install one from outside an already compiled executable, so the new counter and histogram are unavailable in production despite the documented observability contract; initialize an exporter early in application startup or expose a real recorder configuration path.

AGENTS.md reference: AGENTS.md:L376-L378

Useful? React with 👍 / 👎.

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

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

Inline comments:
In `@docs/developers-guide.md`:
- Around line 2570-2577: Update the netsuke_config_load_duration_seconds
documentation to state that it records one sample per startup that reaches
configuration resolution, matching the timing and record_config_load_metrics
behavior in main. Do not claim coverage of every startup attempt unless the
timer is moved before parse_cli_or_exit and parse failures are recorded.

In `@src/main_tests.rs`:
- Around line 45-66: Strengthen the metric assertions in the test around
CONFIG_LOAD_TOTAL and CONFIG_LOAD_DURATION_SECONDS: require the counter to
contain exactly one label and that label to be outcome=expected_outcome, then
assert the sole histogram sample equals Duration::from_millis(1).as_secs_f64().
Preserve the existing count and single-sample checks.
- Around line 41-43: Expand the tests around run_with_args to capture metrics
for the successfully merged configuration, the resolve_json_mode_or_exit failure
path, and the merge_cli_or_exit failure path. Replace or supplement the direct
record_config_load_metrics test so each configuration path verifies its
timer/metric behavior, including the call sites associated with lines 103, 114,
and 118.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d920bda5-ce1b-40f9-bedb-5fe1ed469e7f

📥 Commits

Reviewing files that changed from the base of the PR and between 48d1e6c and 700cdd8.

📒 Files selected for processing (3)
  • docs/developers-guide.md
  • src/main.rs
  • src/main_tests.rs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/rstest-bdd (auto-detected)
  • leynos/ortho-config (auto-detected)
  • leynos/whitaker (auto-detected)
  • leynos/shared-actions (auto-detected)

Comment thread docs/developers-guide.md Outdated
Comment on lines +2570 to +2577
- `netsuke_config_load_total` — a counter incremented once per startup
configuration-load attempt. It carries a single label `outcome` with values
`success` or `failure`, where `failure` corresponds to diagnostic-mode
resolution or a `merge_with_config` error. Use it to compute the
configuration-load failure rate.
- `netsuke_config_load_duration_seconds` — a histogram recording the
wall-clock duration of the configuration-load phase in seconds (one sample
per startup). Suggested operator bucket boundaries: `0.001, 0.005, 0.01,

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

Scope the “one sample per startup” claim to the measured phase.

Change the wording to “one sample per startup that reaches configuration resolution”, or start the timer before parse_cli_or_exit if the metric must cover every startup attempt. The supplied src/main.rs path starts timing only after CLI parsing succeeds, and the parse-error branch returns without calling record_config_load_metrics. The current wording can cause operators to under-count startup failures.

Based on the supplied src/main.rs context.

🤖 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 `@docs/developers-guide.md` around lines 2570 - 2577, Update the
netsuke_config_load_duration_seconds documentation to state that it records one
sample per startup that reaches configuration resolution, matching the timing
and record_config_load_metrics behavior in main. Do not claim coverage of every
startup attempt unless the timer is moved before parse_cli_or_exit and parse
failures are recorded.

Comment thread src/main_tests.rs Outdated
Comment on lines +41 to +43
let metrics = captured_metrics(|| {
record_config_load_metrics(Duration::from_millis(1), succeeded);
});

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/main_tests.rs --items all --type function

rg -n -C 8 --type rust \
  '\b(run_with_args|resolve_json_mode_or_exit|merge_cli_or_exit|record_config_load_metrics|captured_metrics)\b' \
  src/main.rs src/main_tests.rs

Repository: leynos/netsuke

Length of output: 10156


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- src/main_tests.rs ---'
sed -n '1,90p' src/main_tests.rs

printf '%s\n' '--- src/main.rs startup path ---'
sed -n '70,130p' src/main.rs

printf '%s\n' '--- configuration and startup tests ---'
rg -n -C 6 --type rust \
  'run_with_args|merge_with_config|resolve_merged_json|configuration.*fail|config.*fail|ConfigError|ExitCode' \
  src tests

printf '%s\n' '--- test layout ---'
find tests -maxdepth 2 -type f -print 2>/dev/null | sort

Repository: leynos/netsuke

Length of output: 50370


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- startup integration-test helpers ---'
sed -n '1,145p' tests/startup_diagnostics_tests.rs

printf '%s\n' '--- config-driven merge-failure tests ---'
sed -n '190,275p' tests/logging_stderr/json.rs
sed -n '330,380p' tests/advanced_usage_tests.rs

printf '%s\n' '--- CLI command and config defaults ---'
rg -n -C 5 --type rust \
  'enum Commands|struct Cli|default_value|command\(' \
  src/cli/parser.rs src/cli/config.rs src/runner.rs

printf '%s\n' '--- metric recorder and run_with_args references ---'
rg -n --type rust \
  'with_local_recorder|CONFIG_LOAD_TOTAL|CONFIG_LOAD_DURATION_SECONDS|run_with_args' \
  src tests

Repository: leynos/netsuke

Length of output: 18228


🏁 Script executed:

set -euo pipefail

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

main = Path("src/main.rs").read_text()
tests = Path("src/main_tests.rs").read_text()

body = re.search(
    r"fn run_with_args\(.*?\n\}\n\n/// Handle for adjusting",
    main,
    re.S,
).group(0)

print("run_with_args metric calls:")
for line_no, line in enumerate(body.splitlines(), 1):
    if "record_config_load_metrics" in line:
        print(f"  {line_no}: {line.strip()}")

print("run_with_args configuration-path calls:")
for name in ("parse_cli_or_exit", "resolve_json_mode_or_exit", "merge_cli_or_exit"):
    print(f"  {name}: {len(re.findall(r'\\b' + name + r'\\b', body))}")

print("main_tests run_with_args references:")
for line_no, line in enumerate(tests.splitlines(), 1):
    if "run_with_args" in line:
        print(f"  {line_no}: {line.strip()}")

print("direct metric-helper test references:")
for line_no, line in enumerate(tests.splitlines(), 1):
    if "record_config_load_metrics" in line:
        print(f"  {line_no}: {line.strip()}")
PY

printf '%s\n' '--- all metric references ---'
rg -n --type rust \
  'with_local_recorder|CONFIG_LOAD_TOTAL|CONFIG_LOAD_DURATION_SECONDS|record_config_load_metrics|run_with_args' \
  src tests || true

Repository: leynos/netsuke

Length of output: 387


🏁 Script executed:

set -euo pipefail

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

main = Path("src/main.rs").read_text()
tests = Path("src/main_tests.rs").read_text()

match = re.search(
    r"fn run_with_args\(.*?\n\}\n\n/// Handle for adjusting",
    main,
    re.S,
)
if not match:
    raise SystemExit("run_with_args body not found")
body = match.group(0)

print("run_with_args metric calls:")
for line_no, line in enumerate(body.splitlines(), 1):
    if "record_config_load_metrics" in line:
        print(f"  {line_no}: {line.strip()}")

print("run_with_args configuration-path calls:")
for name in ("parse_cli_or_exit", "resolve_json_mode_or_exit", "merge_cli_or_exit"):
    count = len(re.findall(r"\b" + name + r"\b", body))
    print(f"  {name}: {count}")

print("main_tests run_with_args references:")
for line_no, line in enumerate(tests.splitlines(), 1):
    if "run_with_args" in line:
        print(f"  {line_no}: {line.strip()}")

print("direct metric-helper test references:")
for line_no, line in enumerate(tests.splitlines(), 1):
    if "record_config_load_metrics" in line:
        print(f"  {line_no}: {line.strip()}")
PY

printf '%s\n' '--- all metric references ---'
rg -n --type rust \
  'with_local_recorder|CONFIG_LOAD_TOTAL|CONFIG_LOAD_DURATION_SECONDS|record_config_load_metrics|run_with_args' \
  src tests || true

Repository: leynos/netsuke

Length of output: 2705


Exercise run_with_args across all configuration paths.

Capture metrics while testing a successfully merged configuration, a resolve_json_mode_or_exit failure, and a merge_cli_or_exit failure. The direct record_config_load_metrics test cannot detect a missing timer or call at lines 103, 114, or 118.

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

In `@src/main_tests.rs` around lines 41 - 43, Expand the tests around
run_with_args to capture metrics for the successfully merged configuration, the
resolve_json_mode_or_exit failure path, and the merge_cli_or_exit failure path.
Replace or supplement the direct record_config_load_metrics test so each
configuration path verifies its timer/metric behavior, including the call sites
associated with lines 103, 114, and 118.

Source: Coding guidelines

Comment thread src/main_tests.rs Outdated
Comment on lines +45 to +66
let counter = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_TOTAL)
.expect("config-load counter should be recorded");
assert!(
counter
.1
.iter()
.any(|label| { label.key() == "outcome" && label.value() == expected_outcome }),
"counter should carry outcome={expected_outcome}: {:?}",
counter.1
);
assert_eq!(counter.2, DebugValue::Counter(1));

let histogram = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_DURATION_SECONDS)
.expect("config-load duration histogram should be recorded");
let DebugValue::Histogram(samples) = &histogram.2 else {
panic!("expected a histogram value, got {:?}", histogram.2);
};
assert_eq!(samples.len(), 1, "exactly one duration sample expected");

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 | 🟡 Minor | ⚡ Quick win

Assert the complete metric contract.

Assert that the counter has exactly one outcome label. Assert that the
histogram sample equals Duration::from_millis(1).as_secs_f64(). The current
test passes if the metric records zero, uses another unit, or adds labels.

Proposed test assertions
     assert!(
         counter
             .1
             .iter()
             .any(|label| { label.key() == "outcome" && label.value() == expected_outcome }),
         "counter should carry outcome={expected_outcome}: {:?}",
         counter.1
     );
+    assert_eq!(counter.1.len(), 1, "counter must have only the outcome label");
     assert_eq!(counter.2, DebugValue::Counter(1));
 
     let histogram = metrics
         .iter()
         .find(|(name, _, _)| name == CONFIG_LOAD_DURATION_SECONDS)
         .expect("config-load duration histogram should be recorded");
     let DebugValue::Histogram(samples) = &histogram.2 else {
         panic!("expected a histogram value, got {:?}", histogram.2);
     };
-    assert_eq!(samples.len(), 1, "exactly one duration sample expected");
+    assert_eq!(samples.as_slice(), &[Duration::from_millis(1).as_secs_f64()]);

As per coding guidelines, validate new features with unit tests.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let counter = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_TOTAL)
.expect("config-load counter should be recorded");
assert!(
counter
.1
.iter()
.any(|label| { label.key() == "outcome" && label.value() == expected_outcome }),
"counter should carry outcome={expected_outcome}: {:?}",
counter.1
);
assert_eq!(counter.2, DebugValue::Counter(1));
let histogram = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_DURATION_SECONDS)
.expect("config-load duration histogram should be recorded");
let DebugValue::Histogram(samples) = &histogram.2 else {
panic!("expected a histogram value, got {:?}", histogram.2);
};
assert_eq!(samples.len(), 1, "exactly one duration sample expected");
let counter = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_TOTAL)
.expect("config-load counter should be recorded");
assert!(
counter
.1
.iter()
.any(|label| { label.key() == "outcome" && label.value() == expected_outcome }),
"counter should carry outcome={expected_outcome}: {:?}",
counter.1
);
assert_eq!(counter.1.len(), 1, "counter must have only the outcome label");
assert_eq!(counter.2, DebugValue::Counter(1));
let histogram = metrics
.iter()
.find(|(name, _, _)| name == CONFIG_LOAD_DURATION_SECONDS)
.expect("config-load duration histogram should be recorded");
let DebugValue::Histogram(samples) = &histogram.2 else {
panic!("expected a histogram value, got {:?}", histogram.2);
};
assert_eq!(samples.as_slice(), &[Duration::from_millis(1).as_secs_f64()]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main_tests.rs` around lines 45 - 66, Strengthen the metric assertions in
the test around CONFIG_LOAD_TOTAL and CONFIG_LOAD_DURATION_SECONDS: require the
counter to contain exactly one label and that label to be
outcome=expected_outcome, then assert the sole histogram sample equals
Duration::from_millis(1).as_secs_f64(). Preserve the existing count and
single-sample checks.

Source: Coding guidelines

leynos added 4 commits August 9, 2026 16:13
Document that `netsuke_config_load_duration_seconds` records one sample
for startups that reach configuration resolution, matching the timer's
placement after CLI parsing.
Describe `NETSUKE_METRICS_LISTEN` for operators, including its socket
syntax, startup timing, process lifetime, default-disabled behaviour,
and startup failures for invalid or unavailable addresses.
Precede the `NETSUKE_METRICS_LISTEN` shell example with its required
`tested-example` marker so documentation example validation can load it.
Install an opt-in Prometheus recorder before configuration resolution so
operators can scrape the startup metrics from the standalone binary.

Cover each configuration path through the startup orchestration and keep
the metric contract assertions exact where timing is deterministic.
codescene-access[bot]

This comment was marked as outdated.

Describe the optional Prometheus recorder and configuration-load metrics at
the application entry-point boundary.
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add metrics instrumentation for config-load failure rates and startup latency

1 participant