feat(observability): validator health metrics + readiness probe - #102
feat(observability): validator health metrics + readiness probe#102luishsr wants to merge 5 commits into
Conversation
P2 of the Network Observability Initiative (axyl-private#428). Adds the validator/
network-health signals the audit found missing — the node already exports Narwhal/
Mysten's Prometheus suite, but the highest-value per-validator and readiness signals
were tracked in-memory and never exposed.
Readiness probe: the healthcheck now serves /readyz — 200 only when the node is voting
(CvvActive) or an operational observer, 503 while a validator is still catching up
(CvvInactive). Liveness (any other path) stays 200. Reads the live NodeMode via a
watch::Receiver from the consensus bus, threaded at the spawn site. Lets a load balancer /
on-call distinguish "process up" from "actually voting in the current epoch".
New metrics (registered into the consensus default_registry, served on --metrics):
- validator_participation{authority} — committed certificates per validator, counted at
the bullshark commit site. A direct per-validator liveness signal, consistent across
nodes (every node commits the same sub-dags). This is the audit's biggest gap
("did validator X vote/certify?").
- vote_request_rejections{authority, reason} — this node's header vote requests rejected
by a peer (from VoteFailureTracker), by rejecting validator and reason
(too_old | epoch_mismatch). Node-local: rising totals mean this node is falling behind.
- committee_size — committee size for the current epoch, set per epoch.
Dashboard: etc/monitoring/.../rayls-consensus.json — a starter Grafana dashboard (epoch,
committee size, bad nodes, round progress, commit-latency p95, leader election, the two
new per-validator series, peers) with auto-provisioning, portable via a datasource
variable. Composes with the etc/monitoring stack from axyl#99.
Refs: raylsnetwork/axyl-private#428
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @luishsr's task in 2m 25s —— View job PR Review: feat(observability): validator health metrics + readiness probeTasks:
SummaryThis is well-scoped observability work. The readiness probe design is sound (watch-channel-based mode propagation, path routing, proper HTTP formatting), and the metrics follow the established Issues1. Startup bind error is silently swallowed —
|
…ion metric Follow-up to the P2 review on #102: - Propagate a healthcheck bind failure (core.rs) instead of `let _ =`-swallowing it. A port already in use would previously start the node with no health endpoint and no signal — defeating the purpose of an explicitly-requested probe. Now returns the error. - Handle each healthcheck connection in its own task (health.rs). The accept loop bounded each request read with a 200ms timeout but still ran it inline, so a client that connects and sends nothing held the loop for the full timeout and delayed every other probe. A per-connection task keeps the accept loop free; the watch::Receiver is cloned per conn. - Reset the accept-loop backoff on a healthy accept (health.rs) so a past transient error doesn't keep the retry delay elevated. - Count cert-covered-skipped too-old rejections under a distinct `too_old_skipped` reason (certifier.rs) rather than dropping them from vote_request_rejections. Otherwise the metric reads zero while a node continuously trips the skip path, masking real trouble. Doc comment on the metric updated to list the three reason values. - Document the `/ready` alias alongside `/readyz` in the health module docs. Reviewer's other two points verified as non-issues, no change: the `authority` label is a ~44-char bs58 of a 32-byte AuthorityIdentifier with cardinality bounded by committee size (not a raw BLS key); and `current_round` (dashboard panel 4) is a real registered PrimaryMetrics gauge on the same default_registry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @luishsr's task in 1m 54s —— View job PR Re-Review: feat(observability): validator health metrics + readiness probeTasks:
Follow-up ReviewThe fix commit ( Issue 1 — Silent bind error ✅ Fixed
Issue 2 — Accept loop blocking for 200ms ✅ Fixed
One minor observation (not blocking): the Issue 3 — Backoff not reset on success ✅ Fixed
Issue 4 — Authority label cardinality ✅ Verified non-issueCommit message confirms Issue 5 —
|
…ric with too_old_skipped) The re-review noted the epoch-mismatch stale-peer early-return dropped the rejection from vote_request_rejections — the same gap just fixed for too_old. Count it under a distinct `epoch_mismatch_stale` reason so the raw rejection rate stays visible across all four paths. Metric doc updated to list all reason values. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @luishsr's task in 1m 14s —— View job PR Re-Review (commit
|
| Path | reason label |
Counts toward demotion |
|---|---|---|
| Too-old, cert store covers it | too_old_skipped |
No |
| Too-old, counted | too_old |
Yes |
| Epoch mismatch, stale peer | epoch_mismatch_stale |
No |
| Epoch mismatch, counted | epoch_mismatch |
Yes |
Doc comment (consensus.rs) — updated to list all four reason values and explain the *_skipped / *_stale distinction. The explanation is clear and accurate.
No further gaps found. I re-read the DagError::TooOldRejectedByPeer and DagError::EpochRejectedByPeer match arms end-to-end — there are no remaining early-return paths that bypass the metric.
Summary
All issues from both prior reviews are now resolved. The PR is clean, well-tested, and the metric coverage is complete. Ready to merge.
…lidator-metrics # Conflicts: # crates/middleware/orchestrator/src/epoch_manager/core.rs
|
Claude finished @luishsr's task in 1m 34s —— View job PR Re-Review (post-merge-sync
|
Security Scan - CodeSeverity: HIGH, CRITICAL No vulnerabilities found View scan results |
…omment_width CI's Format job runs `cargo +nightly fmt --all --check`, whose rustfmt.toml sets comment_width=100 + wrap_comments (nightly-only). One doc-comment line in the readiness module wrapped one word early; rewrap it so the nightly check passes. No code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @luishsr's task in 34s —— View job PR Re-Review (post-sync
|
What
P2 of the Network Observability Initiative (
axyl-private#428). The research audit found the node already exports Narwhal/Mysten's full Prometheus suite — but the highest-value per-validator and readiness signals were tracked in-memory and never exposed. This PR surfaces them.Readiness probe
The healthcheck now serves
/readyz—200only when the node is voting (CvvActive) or an operational observer, and503while a validator is still catching up (CvvInactive). Liveness (any other path) stays200. It reads the liveNodeModevia awatch::Receiverfrom the consensus bus, threaded in at the spawn site. This lets a load balancer / on-call distinguish "process up" from "actually voting in the current epoch" — which the unconditional-200healthcheck couldn't.New metrics (consensus registry, served on
--metrics/metrics_address)validator_participation{authority}— committed certificates per validator, counted at the bullshark commit site (where the sub-dag certificates are already iterated). A direct per-validator liveness signal, and consistent across nodes since every node commits the same sub-dags. This is the audit's biggest gap — "did validator X vote/certify?"vote_request_rejections{authority, reason}— this node's header vote requests rejected by a peer (fromVoteFailureTracker), by rejecting validator and reason (too_old/epoch_mismatch). Node-local: rising totals mean this node is falling behind. Complements the network-consistentvalidator_participation.committee_size— committee size for the current epoch, set at the per-epoch metrics hook.Dashboard
etc/monitoring/.../rayls-consensus.json— a starter Grafana dashboard (9 panels: epoch, committee size, bad nodes, round progress, commit-latency p95, leader election, the two new per-validator series, peers) with auto-provisioning, portable via a datasource variable.Tests
3 tests on the readiness probe (path routing, mode→status mapping, and a live server test flipping
CvvInactive→CvvActiveand seeing503→200). The metrics follow the establishedregister_*_with_registry!idiom; compiles clean acrossconsensus-primary,primary-metrics, andorchestrator.Composition / scope
etc/monitoring/dashboard composes with the monitoring stack in feat(observability): enable metrics via config + operator monitoring setup #99 (this branch is offmain, so feat(observability): enable metrics via config + operator monitoring setup #99'sprometheus.yml/ docker-compose aren't on it yet; they merge cleanly as separate files).ValidatorStatusexport (Active / PendingActivation / Exited — needs an EVMConsensusRegistryread joined viaCommittee) and round-lag as its own metric (currently derived in the dashboard). Tracked under#428.🤖 Generated with Claude Code