spec: add subnet_metrics management canister endpoint - #333
Open
Dfinity-Bjoern wants to merge 8 commits into
Open
spec: add subnet_metrics management canister endpoint#333Dfinity-Bjoern wants to merge 8 commits into
Dfinity-Bjoern wants to merge 8 commits into
Conversation
Proposal for discussion. Adds a subnet_metrics endpoint returning the subnet block height plus the four subnet-wide metrics that are currently only reachable by external users via the certified state tree path /subnet/<subnet_id>/metrics.
|
🤖 Here's your preview: https://hada6-4yaaa-aaaam-abaha-cai.icp0.io |
- Drop the own-subnet restriction: cross-subnet calls are handled by the existing message routing protocol, so no restriction is needed. - Report the subnet's latest certified height rather than the height of the block containing the call, and rename the field to certified_height. - Keep nat for all fields, since consumed_cycles_total cannot be nat64.
Dfinity-Bjoern
commented
Aug 3, 2026
Replace certified_height with block_height, defined as the height of the block in whose execution the call is processed on the target subnet.
mraszyk
reviewed
Aug 3, 2026
|
|
||
| ## Changelog {#changelog} | ||
|
|
||
| ### 0.65.0 (2026-07-31) {$0_65_0} |
Contributor
There was a problem hiding this comment.
this should be updated before merging
mraszyk
reviewed
Aug 3, 2026
mraszyk
reviewed
Aug 3, 2026
mraszyk
reviewed
Aug 3, 2026
Co-authored-by: mraszyk <31483726+mraszyk@users.noreply.github.com>
Co-authored-by: mraszyk <31483726+mraszyk@users.noreply.github.com>
Dfinity-Bjoern
marked this pull request as ready for review
August 4, 2026 07:42
mraszyk
reviewed
Aug 4, 2026
mraszyk
left a comment
Contributor
There was a problem hiding this comment.
LGTM now, I'll approve once this is rolled out to all subnets to prevent accidental merge before that
Dfinity-Bjoern
pushed a commit
to dfinity/ic
that referenced
this pull request
Aug 5, 2026
…reshness Addresses the CI failure and the Copilot review. No production logic changed; this is test code and doc comments only. **Composite-query system test.** `subnet_metrics_composite_query_fails` asserted that the routing rejection's message reaches the caller. It does not: `reject_subnet_message_routing`'s synthesized response is never delivered on the query path, so the universal canister never replies and the outer query fails `CanisterError` / "did not produce a response". This is established platform behaviour of the composite-query arm in `resolve_destination`, not something this change introduced. A control experiment showed `fetch_canister_logs` — which has the identical arm and ships enabled — behaves identically, while `canister_status`, which has no such arm, does deliver its reject (no arm means the request is created and `QueryContext::handle_request`'s reject is delivered normally). The test now asserts the real behaviour and says plainly that this makes it weak: it cannot distinguish the arm from any other failure to reply, and would pass against a stub. The method-specific assertion lives in `resolve_subnet_metrics_rejects_composite_query` in `routing.rs`, which tests `resolve_destination` directly. The division of labour is: the unit test proves the arm, the system test documents user-visible behaviour. The now-inert `.on_reject(...)` is kept deliberately, so that if the platform ever does deliver the reject, the test fails loudly rather than quietly continuing to assert the swallowed behaviour. All five `subnet_metrics` system tests now pass, verified by execution on a Linux host rather than by inspection — including the cross-subnet attribution test, which is the first genuine cross-subnet management-call test in the repo. **Field freshness docs.** Per review, the Rust doc comments described values as "current" when four of the five lag: only `block_height` is current, the other four are as of end-of-previous-round, and `canister_state_bytes` is refreshed only every 10 rounds (so it reads 0 early in a subnet's life). Documented on both `SubnetMetricsResult` and `SubnetMetricsResponse`. The review also asked for the same wording change in the two `ic.did` fixtures. Deliberately not done: those must stay byte-identical to the upstream spec's `public/references/ic.did`. That wording fix belongs in dfinity/developer-docs#333, which already carries an open item on imprecise gauge-vs-counter wording. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Draft for discussion, not ready to merge. Proposes a new EXPERIMENTAL management canister endpoint
subnet_metricsthat returns subnet-wide metrics to canisters.Motivating request: move more dashboard data collection on chain, starting with the subnet block height and the total cycles burned on a subnet.
What already exists
consumed_cycles_totalat/subnet/<subnet_id>/metrics, alongsidenum_canisters,canister_state_bytes, andupdate_transactions_total. But that path is readable only by external users viaread_stateat/api/v2|v3/subnet/<subnet_id>/read_state, and only when the effective subnet id matches. There is no way for a canister to read it, since no System API or management canister method exposes state tree paths to canister code.subnet_inforeturns onlyreplica_versionandregistry_version.update_transactions_totalcounts messages, not blocks. Summingnum_blocks_proposed_totalfromnode_metrics_historyis at best an approximation: counted only since the metric was introduced, sampled at most daily, capped at 60 samples, and documented as resetting to 0 when a node disappears and reappears. The/api/v2/statusendpoint lists block height explicitly as a possible future addition.So the endpoint adds one genuinely new value (the block height) and makes four existing values reachable from canisters, which is why all five are bundled rather than just the two originally requested.
Proposed interface
Callable by canisters only, replicated calls only,
subnet_idmay be any subnet.Resolved in review
subnet_idto be the caller's own subnet. Removed: cross-subnet calls are carried by the existing message routing protocol, so the request reaches the target subnet and its response comes back without any special handling. The abstract-behavior condition tyingsubnet_idto the caller's subnet is gone too.block_heightis the height of the block in whose execution the call is processed. Since a call naming a remote subnet is routed there and processed there, this is the target subnet's current height, not the caller's. Certified height was considered and rejected.natthroughout.nat64was preferred for consistency withnode_metricsand for fixed-width parsing, butconsumed_cycles_totalisu128in the implementation (which is exactly why the state tree splits it into a lownatplus an optional highnatunder CBOR), so it cannot benat64. Rather than mix widths or reproduce the CBOR low/high workaround in a format that has unboundednatnatively, all five fields arenat. This also matchescanister_statusandcanister_metrics, which usenatthroughout.subnet_info. Folding the metrics in as a nestedmetricsfield was considered. Against it: 0.61.0 addedcanister_metricsas a new endpoint rather than extendingcanister_status, which is the same situation; andsubnet_infois not EXPERIMENTAL, so nesting an evolving record inside it means the caveat either leaks onto the whole endpoint or hides in a per-field note. Merging later remains backward compatible if we change our minds; splitting later would not be.Still open
num_canistersandcanister_state_bytesare documented as current values;consumed_cycles_totalandupdate_transactions_totalas counters. The existing state tree description ofcanister_state_bytesinindex.mdsays "since this subnet was created", which reads as a counter and is probably imprecise wording. If so it should be fixed there too. Marked in the diff.Files changed
public/references/ic.did- newsubnet_metrics_args/subnet_metrics_resulttypes and the service method.didc checkpasses.docs/references/ic-interface-spec/management-canister.md- new normativeIC method subnet_metricssection.docs/references/ic-interface-spec/abstract-behavior.md- newIC Management Canister: Subnet Metricssemantics block.docs/references/ic-interface-spec/changelog.md-0.65.0entry. Version and date need confirming at merge time.docs/references/management-canister.md- non-normative reference page entry.No changes needed to the ingress authorization list,
Effective canister id, orEffective subnet id, because the method is not callable via ingress messages.Follow-ups outside this repo
ic-cdkand Motoko management canister bindings./subnet/<subnet_id>/metricsin the state tree as well, so external users get it in certified form without deploying a canister.npm run buildpasses.