Rationale
Today the only way to inspect smoldot's runtime state is via logs (system_health/system_peers aside, which expose almost nothing). Logs are often disabled in the field, verbose, and require reconstructing aggregate state by eye. This is especially painful in the browser, where you can't attach a debugger and bug reports come from users you can't instrument.
A small metrics surface lets us judge node state at a glance — peer churn, request failures, ban rate, sync progress, runtime-compilation cost — without enabling debug logging. Several useful internals are already measured and only log!ed (bytes/CPU, runtime compilation time, ping latency); this mostly aggregates what already exists.
Solution sketch
- A hand-rolled
Arc<Metrics> of counters/gauges, no new dependencies, incremented at existing event sites.
- Surfaced over the existing JSON-RPC interface (works identically in wasm and full-node; no Prometheus HTTP server needed):
- Pull:
sudo_unstable_metrics() → snapshot of all metric families.
- Push:
sudo_unstable_metricsWatch(intervalMs) → periodic absolute snapshots (the client diffs for rates; this enables simple graphs in an enclosing app's debug window).
- Naming policy = Prometheus conventions (counter/gauge distinction,
Total suffix, base units, dimensions as labels), camelCase to match the repo. No high-cardinality labels (no peerId/multiaddr/hash) — aggregate only; per-peer detail stays in system_peers.
Sample metrics (illustrative, not exhaustive)
- Network:
networkConnectionsOpenedTotal{direction}, networkRequestsTotal{protocol,outcome}, networkPeerBansTotal{reason}, networkAddressesDroppedTotal{reason}, gauge networkGossipPeersConnected{chain}
- Sync:
syncBlocksVerifiedTotal{outcome}, syncVerificationErrorsTotal{kind,reason}, gauges syncBestBlockHeight / syncFinalizedBlockHeight
- Runtime:
runtimeCompilationSeconds, runtimeCacheHitsTotal (compilation timing already measured today)
- JSON-RPC / tx:
jsonrpcRequestsTotal{method,outcome}, transactionsDroppedTotal{reason}
- Errors are first-class: classified by who's at fault (peer misbehavior / transient / local bug).
Usage scenarios
- Hidden debug view in the embedding app. A diagnostic screen subscribes via
metricsWatch and renders live graphs (peer count, request failures, ban rate, sync height, runtime-compilation time) — turning "node feels stuck" into a readable picture without enabling trace logs.
- Centralized monitoring. An embedder/operator scrapes
sudo_unstable_metrics and ships it to Grafana (full-node exposes it as a Prometheus /metrics endpoint later — a pure formatting layer over the same data), for fleet-wide dashboards and alerting.
- Large-scale / automated testing. Test harnesses assert on aggregate counters across many nodes — e.g. ban rate stays near zero, finality lag stays bounded, request-failure ratio under a threshold — to catch regressions like the recent
UnknownTargetBlock false-ban, which today are only visible by eyeballing logs.
Scope / open questions
- v1: counters + gauges + already-measured durations (as count+sum). Full bucketed histograms deferred to v2.
Rationale
Today the only way to inspect smoldot's runtime state is via logs (
system_health/system_peersaside, which expose almost nothing). Logs are often disabled in the field, verbose, and require reconstructing aggregate state by eye. This is especially painful in the browser, where you can't attach a debugger and bug reports come from users you can't instrument.A small metrics surface lets us judge node state at a glance — peer churn, request failures, ban rate, sync progress, runtime-compilation cost — without enabling debug logging. Several useful internals are already measured and only
log!ed (bytes/CPU, runtime compilation time, ping latency); this mostly aggregates what already exists.Solution sketch
Arc<Metrics>of counters/gauges, no new dependencies, incremented at existing event sites.sudo_unstable_metrics()→ snapshot of all metric families.sudo_unstable_metricsWatch(intervalMs)→ periodic absolute snapshots (the client diffs for rates; this enables simple graphs in an enclosing app's debug window).Totalsuffix, base units, dimensions as labels), camelCase to match the repo. No high-cardinality labels (no peerId/multiaddr/hash) — aggregate only; per-peer detail stays insystem_peers.Sample metrics (illustrative, not exhaustive)
networkConnectionsOpenedTotal{direction},networkRequestsTotal{protocol,outcome},networkPeerBansTotal{reason},networkAddressesDroppedTotal{reason}, gaugenetworkGossipPeersConnected{chain}syncBlocksVerifiedTotal{outcome},syncVerificationErrorsTotal{kind,reason}, gaugessyncBestBlockHeight/syncFinalizedBlockHeightruntimeCompilationSeconds,runtimeCacheHitsTotal(compilation timing already measured today)jsonrpcRequestsTotal{method,outcome},transactionsDroppedTotal{reason}Usage scenarios
metricsWatchand renders live graphs (peer count, request failures, ban rate, sync height, runtime-compilation time) — turning "node feels stuck" into a readable picture without enabling trace logs.sudo_unstable_metricsand ships it to Grafana (full-node exposes it as a Prometheus/metricsendpoint later — a pure formatting layer over the same data), for fleet-wide dashboards and alerting.UnknownTargetBlockfalse-ban, which today are only visible by eyeballing logs.Scope / open questions