Skip to content

spec: add subnet_metrics management canister endpoint - #333

Open
Dfinity-Bjoern wants to merge 8 commits into
mainfrom
docs/subnet-metrics-endpoint
Open

spec: add subnet_metrics management canister endpoint#333
Dfinity-Bjoern wants to merge 8 commits into
mainfrom
docs/subnet-metrics-endpoint

Conversation

@Dfinity-Bjoern

@Dfinity-Bjoern Dfinity-Bjoern commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Draft for discussion, not ready to merge. Proposes a new EXPERIMENTAL management canister endpoint subnet_metrics that 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

  • Cycles burned: already in the certified state tree, as consumed_cycles_total at /subnet/<subnet_id>/metrics, alongside num_canisters, canister_state_bytes, and update_transactions_total. But that path is readable only by external users via read_state at /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.
  • Height: not exposed anywhere in the spec. subnet_info returns only replica_version and registry_version. update_transactions_total counts messages, not blocks. Summing num_blocks_proposed_total from node_metrics_history is 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/status endpoint 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

type subnet_metrics_args = record { subnet_id : principal };

type subnet_metrics_result = record {
    block_height : nat;
    num_canisters : nat;
    canister_state_bytes : nat;
    consumed_cycles_total : nat;
    update_transactions_total : nat;
};

Callable by canisters only, replicated calls only, subnet_id may be any subnet.

Resolved in review

  • Any subnet, no restriction. An earlier revision required subnet_id to 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 tying subnet_id to the caller's subnet is gone too.
  • Current block height. block_height is 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.
  • No cycle fee and no explicit PocketIC support needed. Both follow-ups dropped.
  • nat throughout. nat64 was preferred for consistency with node_metrics and for fixed-width parsing, but consumed_cycles_total is u128 in the implementation (which is exactly why the state tree splits it into a low nat plus an optional high nat under CBOR), so it cannot be nat64. Rather than mix widths or reproduce the CBOR low/high workaround in a format that has unbounded nat natively, all five fields are nat. This also matches canister_status and canister_metrics, which use nat throughout.
  • Kept separate from subnet_info. Folding the metrics in as a nested metrics field was considered. Against it: 0.61.0 added canister_metrics as a new endpoint rather than extending canister_status, which is the same situation; and subnet_info is 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

  • Gauge vs counter. num_canisters and canister_state_bytes are documented as current values; consumed_cycles_total and update_transactions_total as counters. The existing state tree description of canister_state_bytes in index.md says "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.
  • First consensus-level concept in the interface spec. "Block height" appears nowhere else in the specification; the abstract behavior models no blocks at all. The new section therefore defines the term locally and notes that heights of different subnets are unrelated. Worth a sanity check that we are comfortable introducing this vocabulary here.

Files changed

  • public/references/ic.did - new subnet_metrics_args / subnet_metrics_result types and the service method. didc check passes.
  • docs/references/ic-interface-spec/management-canister.md - new normative IC method subnet_metrics section.
  • docs/references/ic-interface-spec/abstract-behavior.md - new IC Management Canister: Subnet Metrics semantics block.
  • docs/references/ic-interface-spec/changelog.md - 0.65.0 entry. 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, or Effective subnet id, because the method is not callable via ingress messages.

Follow-ups outside this repo

  • ic-cdk and Motoko management canister bindings.
  • Optionally add the block height to /subnet/<subnet_id>/metrics in the state tree as well, so external users get it in certified form without deploying a canister.

npm run build passes.

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.
@github-actions github-actions Bot added the interface-spec Changes to the IC interface specification label Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

🤖 Here's your preview: https://hada6-4yaaa-aaaam-abaha-cai.icp0.io

@Dfinity-Bjoern
Dfinity-Bjoern requested a review from dylancm4 July 31, 2026 10:05
- 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.
Comment thread docs/references/ic-interface-spec/management-canister.md Outdated
Dfinity-Bjoern and others added 2 commits August 3, 2026 09:55
Replace certified_height with block_height, defined as the height of the
block in whose execution the call is processed on the target subnet.

## Changelog {#changelog}

### 0.65.0 (2026-07-31) {$0_65_0}

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.

this should be updated before merging

Comment thread docs/references/ic-interface-spec/management-canister.md Outdated
Comment thread docs/references/ic-interface-spec/management-canister.md Outdated
Comment thread docs/references/ic-interface-spec/management-canister.md Outdated
Dfinity-Bjoern and others added 3 commits August 3, 2026 14:04
Co-authored-by: mraszyk <31483726+mraszyk@users.noreply.github.com>
Co-authored-by: mraszyk <31483726+mraszyk@users.noreply.github.com>
@Dfinity-Bjoern
Dfinity-Bjoern marked this pull request as ready for review August 4, 2026 07:42
@Dfinity-Bjoern
Dfinity-Bjoern requested review from a team as code owners August 4, 2026 07:42

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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

interface-spec Changes to the IC interface specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants