Skip to content

spec: add subnet_metrics management canister endpoint - #333

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

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

Conversation

@Dfinity-Bjoern

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

Copy link
Copy Markdown

Summary

Specifies the EXPERIMENTAL management canister endpoint subnet_metrics, which returns subnet-wide metrics to canisters: the subnet block height plus the four aggregates the certified state tree already exposes at /subnet/<subnet_id>/metrics.

The block height is genuinely new; the other four exist in the state tree but are unreachable from canister code, since no System API or management canister method exposes state tree paths. That is why all five are bundled rather than only the block height and consumed_cycles_total originally requested.

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, no cycle fee, and subnet_id may name any subnet (the call is routed there and answered by that subnet).

Field semantics

  • Only block_height is current: it is the height of the block in whose execution the call is processed. The other four are end-of-round aggregates, so a call in round N sees round N-1, and they are not refreshed at the same rate, so they need not be mutually consistent.
  • canister_state_bytes refreshes only at heights that are multiples of 10, so it can lag the other fields by up to 10 blocks and reads 0 before the first refresh.
  • consumed_cycles_total is not a counter: its per-canister part is a gauge that prepayment raises and refund lowers, so the total can decrease. update_transactions_total is a true counter.

The state tree description in index.md covers the same aggregates, so it gets the same corrections.

Verification

Every claim was checked field by field against 7360f8f3, the revision running on all 42 subnets (implementing PR dfinity/ic#11032, first release release-2026-09-03_04-41-base, rollout completed 2026-09-14). Candid shape, names, types and order match rs/types/management_canister_types/tests/ic.did; didc check public/references/ic.did passes.

Files changed

  • public/references/ic.did - new arg/result types and service method.
  • docs/references/ic-interface-spec/management-canister.md - normative subnet_metrics section.
  • docs/references/ic-interface-spec/abstract-behavior.md - semantics block.
  • docs/references/ic-interface-spec/index.md - corrected descriptions of the same aggregates under /subnet/<subnet_id>/metrics.
  • docs/references/ic-interface-spec/changelog.md - 0.68.0 (2026-09-14) entry.
  • docs/references/management-canister.md - non-normative reference entry.

The ingress authorization list, Effective canister id and Effective subnet id need no changes, since the method is not callable via ingress.

Open questions

  • First consensus-level concept in the spec. "Block height" appears nowhere else, and the abstract behavior models no blocks, so the new section defines the term locally and notes that heights of different subnets are unrelated. Worth confirming we are comfortable introducing this vocabulary here.
  • The 10-block interval is stated as a concrete constant. If the spec should not pin it, that sentence can go back to a hedge.

Follow-ups outside this repo

  • ic-cdk and Motoko management canister bindings.
  • Optionally expose the block height at /subnet/<subnet_id>/metrics too, so external users get it in certified form without deploying a canister.

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.
Comment thread docs/references/ic-interface-spec/changelog.md Outdated
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>
…ndpoint

# Conflicts:
#	docs/references/ic-interface-spec/changelog.md
Verified the subnet_metrics text field by field against the deployed
replica (release-2026-09-03_04-41-base) and corrected three claims:

* Only block_height describes the block in whose execution the call is
  processed. The other four fields are aggregates refreshed at block
  boundaries, so they describe an earlier block and need not be
  mutually consistent.
* canister_state_bytes is refreshed only every 10 blocks, at heights
  that are multiples of 10, and reads 0 until the first refresh after
  the subnet was created.
* consumed_cycles_total is not a monotonic counter: it nets out refunds
  of cycles charged in advance, so it can decrease. It also covers
  deleted canisters and cycles consumed on behalf of the subnet itself.
  update_transactions_total is monotonically non-decreasing.

The state tree description of the same aggregates in index.md is
corrected accordingly, including canister_state_bytes, which was
described as accumulating since the subnet was created.
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