Skip to content

spec: add subnet_metrics management canister endpoint - #333

Merged
mraszyk merged 20 commits into
mainfrom
docs/subnet-metrics-endpoint
Oct 7, 2026
Merged

mraszyk merged 20 commits into
mainfrom
docs/subnet-metrics-endpoint

Conversation

@Dfinity-Bjoern

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

Copy link
Copy Markdown
Contributor

Summary

Specifies the EXPERIMENTAL management canister endpoint subnet_metrics, which returns subnet-wide metrics to canisters: the subnet block height, the four aggregates the certified state tree already exposes at /subnet/<subnet_id>/metrics, and the total number of instructions the subnet accounted for when executing its blocks.

The block height is new; the four aggregates 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 they are bundled rather than only the block height and consumed_cycles_total originally requested.

Covers two replica PRs:

Changes

  • ic-interface-spec/management-canister.md: new section for subnet_metrics with the full field semantics.
  • ic-interface-spec/abstract-behavior.md: new transition for subnet_metrics; the metric values are implementation-specific and attached cycles are refunded.
  • ic-interface-spec/index.md: new definitions of cost schedules (normal and free; a free schedule waives resource charges only, such as instruction execution, memory usage, and HTTPS outcalls, while cycles burned via ic0.cycles_burn128, deleted canisters' remaining cycle balances, and the cycles of dropped messages or refunds are still lost) and nominal cycles (the normal-schedule amount for resource charges, and the amount lost for cycles that are lost under either schedule), and corrected descriptions of the four aggregates under /subnet/<subnet_id>/metrics (current values vs. counters, the 10-block refresh of canister_state_bytes, the full composition of consumed_cycles_total, as a list of its four summands), so the state tree and the method describe the same quantities.
  • ic-interface-spec/changelog.md: version 0.69.0 entry covering the new endpoint, the new definitions, and the clarified state tree metrics (still the next version after merging the latest main, whose newest entry is 0.68.0).
  • references/management-canister.md: condensed reference entry linking to the spec.
  • public/references/ic.did: new types and method, with field comments.

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;
    million_round_instructions_total : nat;
};

Callable by canisters only (not via ingress), and subnet_id may name any subnet, not only the caller's.

Field semantics

  • Only block_height describes the block in whose execution the call is processed. The other five are aggregates refreshed at block boundaries, so they describe an earlier block; they are not refreshed at the same rate, so they need not be mutually consistent.
  • num_canisters and canister_state_bytes are current values, not counters. canister_state_bytes refreshes only every 10 blocks, at heights that are multiples of 10. It reads 0 before the first refresh.
  • consumed_cycles_total is measured in nominal cycles: resource charges are recorded at normal cost-schedule rates even on a subnet with a free cost schedule, where no cycles are deducted from canister balances. It sums the historical consumption of the canisters currently on the subnet, the historical consumption of the canisters deleted on the subnet plus their remaining cycle balances at deletion, the cycles charged at the subnet level for HTTPS outcalls, threshold signature requests, and vetKD requests, and the cycles lost when messages or refunds are dropped (for example, because their recipient no longer exists). Refunds of prepaid charges reduce the total. Subnet splitting redistributes canisters' histories, so the total can decrease and can include consumption from before the receiving subnet was created. It is not a count of cycles actually burned on this subnet.
  • update_transactions_total counts messages executed in replicated mode. It and million_round_instructions_total are monotonically non-decreasing counters covering the subnet's lifetime, or the period since each metric was introduced for subnets that predate it.
  • million_round_instructions_total is reported in units of one million and rounded up: a value of 42 represents 41,000,001 through 42,000,000 instructions. It counts the instructions accounted for when executing the subnet's blocks. Besides the executed Wasm instructions, it covers the fixed per-execution and per-canister overheads charged by the scheduler and the charges for work performed outside of Wasm execution (compilation, chunk assembly, snapshots), so it is not a Wasm instruction meter.
  • block_height and million_round_instructions_total are the two fields with no path under /subnet/<subnet_id>/metrics, so they are available only through this method and cannot be verified against a certificate.

Follow-ups outside this repo

  • ic-cdk and Motoko management canister bindings.
  • dfinity/ic: the million_round_instructions_total comment in rs/types/management_canister_types/tests/ic.did says "Unlike the four fields above", but five fields precede it and block_height also has no state tree path.

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
@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>
mraszyk and others added 5 commits September 14, 2026 08:06
…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.
…ndpoint

# Conflicts:
#	docs/references/ic-interface-spec/changelog.md
Documents dfinity/ic#11587, which adds a sixth field to the endpoint this
branch specifies. It reports the total instructions the subnet accounted for
across the execution phases of all rounds, in units of one million and rounded
up, and unlike the four aggregates it has no path under
`/subnet/<subnet_id>/metrics` in the certified state tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mraszyk

mraszyk commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Merged origin/main into the branch and folded in dfinity/ic#11587, which adds a sixth field to this endpoint.

Conflict resolution: the only conflict was in changelog.md. Main took 0.68.0 (2026-09-14) for the flexible outcalls release, so this entry is renumbered to 0.69.0 (2026-09-17).

New field, million_round_instructions_total (nat): the total instructions the subnet accounted for across the execution phases of all rounds, in units of one million and rounded up. Besides the executed Wasm instructions it covers the fixed per-execution and per-canister overheads charged by the scheduler and the charges for work performed outside of Wasm execution (compilation, chunk assembly, snapshots), so it is not a Wasm instruction meter. It is a true counter and, like the other counters, covers the subnet's whole lifetime or the period since the metric was introduced for subnets that predate it.

Claims adjusted, because the field breaks the previous four-plus-one split:

  • "All fields except block_height report the same quantities that the certified state tree exposes" is now false. The four aggregates are named explicitly, and block_height and million_round_instructions_total are called out as having no path under /subnet/<subnet_id>/metrics, so they are available only through this method.
  • "The other four fields are aggregates refreshed at block boundaries" becomes five, in the spec section, the non-normative reference, and the ic.did type comment.
  • The lifetime-coverage sentence now lists all three counters.

didc check public/references/ic.did passes. The abstract behavior block needs no change: it leaves the metrics values implementation-specific.

The endpoint was verified against 7360f8f3 as before; the new field against 2788b2b1, the merge commit of dfinity/ic#11587.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mraszyk and others added 5 commits September 25, 2026 12:21
The replica adds each deleted canister's remaining balance to the
consumed total, not only the cycles it was charged. Say so wherever the
field is described, and move the 0.69.0 changelog date to 2026-09-28.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cs reference entry

Spell out what "consumption on behalf of the subnet itself" covers in the
consumed_cycles_total descriptions: HTTPS outcall, threshold signature and
vetKD charges accounted for at the subnet level, and cycles attached to
dropped best-effort responses. Clarify which deductions the cost schedule
does not affect, align the state tree's update_transactions_total wording
with the management canister section, reorder the ic.did comment so the
field description comes first, shorten the reference page entry to the
field list plus a link to the spec, and drop blank lines inside the abstract
behavior code fences.
The DroppedMessages use case covers more than dropped best-effort
responses: refunds whose recipient canister no longer exists and
undeliverable responses also add to it.
Comment thread docs/references/ic-interface-spec/changelog.md Outdated
Comment thread docs/references/ic-interface-spec/index.md Outdated
Comment thread docs/references/ic-interface-spec/index.md Outdated
Comment thread docs/references/ic-interface-spec/index.md
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
Comment thread docs/references/management-canister.md Outdated
Comment thread public/references/ic.did Outdated
Comment thread public/references/ic.did Outdated
Restructure consumed_cycles_total as a list of summands, describe the
instruction total in terms of blocks instead of rounds, extend the cost
schedule and nominal cycles definitions to explicitly burned and lost
cycles, and expand the changelog entry.
@mraszyk

mraszyk commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Feedback addressed (a100725), all ten suggestions from @eichhorl applied as proposed:

  • changelog.md: entry now says nominal cycles, names the two fields without a state tree path, and has new bullets for the cost schedule / nominal cycles definitions and the clarified state tree metrics
  • index.md: cost schedules name HTTPS outcalls and link ic0.cycles_burn128; nominal cycles cover explicitly burned and lost cycles; consumed_cycles_total in the state tree is a list of summands
  • management-canister.md (spec): same consumed_cycles_total restructuring; million_round_instructions_total is described in terms of blocks; split long sentences
  • references/management-canister.md: blocks instead of rounds, "never decrease", dropped the "up to 10 blocks behind" claim
  • ic.did: reflowed the consumed_cycles_total comment; "Like block_height" instead of "Unlike the four fields above"

didc check public/references/ic.did and npm run build pass.

@mraszyk
mraszyk merged commit 3694788 into main Oct 7, 2026
11 checks passed
@mraszyk
mraszyk deleted the docs/subnet-metrics-endpoint branch October 7, 2026 13:08
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.

3 participants