Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
412d5b3
spec: add subnet_metrics management canister endpoint
Jul 31, 2026
da04d01
spec: address review feedback on subnet_metrics
Jul 31, 2026
f9861d3
Remove unnecessary clarification
Dfinity-Bjoern Aug 3, 2026
a4f8325
spec: use current block height in subnet_metrics
Aug 3, 2026
b09b864
Shorter and more precise formulation.
Dfinity-Bjoern Aug 3, 2026
62c46b8
Apply suggestion from @mraszyk
Dfinity-Bjoern Aug 3, 2026
97925b5
Apply suggestion from @mraszyk
Dfinity-Bjoern Aug 3, 2026
10ab24c
Merge branch 'main' into docs/subnet-metrics-endpoint
mraszyk Aug 4, 2026
31d2beb
Merge remote-tracking branch 'origin/main' into docs/subnet-metrics-e…
mraszyk Sep 14, 2026
915aa21
spec: align subnet_metrics field semantics with the implementation
mraszyk Sep 14, 2026
b81b26c
spec: drop burned_cycles comparison from subnet_metrics
mraszyk Sep 14, 2026
5461116
Merge remote-tracking branch 'origin/main' into docs/subnet-metrics-e…
mraszyk Sep 17, 2026
2a94ddb
spec: add million_round_instructions_total to subnet_metrics
mraszyk Sep 17, 2026
bf13465
spec: merge the subnet_metrics changelog bullets into one
mraszyk Sep 17, 2026
ffd214b
spec: count deleted canisters' leftover balance in consumed_cycles_total
mraszyk Sep 25, 2026
533a42d
spec: clarify nominal cycles and subnet metrics semantics
mraszyk Sep 25, 2026
9b5aa90
spec: name the subnet-level cycle use cases and trim the subnet_metri…
mraszyk Sep 25, 2026
bc8fa6c
Merge remote-tracking branch 'origin/main' into docs/subnet-metrics-e…
mraszyk Oct 7, 2026
173675d
spec: count all dropped-message cycles in consumed_cycles_total
mraszyk Oct 7, 2026
a100725
spec: apply review suggestions to the subnet_metrics docs
mraszyk Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/references/ic-interface-spec/abstract-behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -3198,6 +3198,40 @@ S with

```

#### IC Management Canister: Subnet Metrics

:::note

The subnet metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way.

:::

The management canister returns subnet-wide metrics given a subnet ID. The definition of the metrics values
is not captured in this formal semantics.

Conditions

```html
S.messages = Older_messages · CallMessage M · Younger_messages
(M.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ M.queue)
M.callee = ic_principal
M.method_name = 'subnet_metrics'
M.arg = candid(A)
R = <implementation-specific>
```

State after

```html
S with
messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = M.origin
response = Reply (candid(R))
refunded_cycles = M.transferred_cycles
}
```

#### IC Management Canister: Subnet information

The management canister returns subnet metadata given a subnet ID.
Expand Down
20 changes: 20 additions & 0 deletions docs/references/ic-interface-spec/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ sidebar:

## Changelog {#changelog}

### 0.69.0 (2026-09-28) {$0_69_0}
* New management canister endpoint `subnet_metrics` returning subnet-wide metrics for a
given subnet: the current block height, the number of canisters, the total canister
state size, the total nominal cycles consumed, the total number of processed
transactions, and the total number of instructions accounted for when executing the
subnet's blocks (`million_round_instructions_total`). All metrics except the block height
and the instruction total were previously readable only by external users, via the
certified state tree path `/subnet/<subnet_id>/metrics`. The instruction total is reported
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. It also
covers the charges for work performed outside of Wasm execution, so it is not a Wasm
instruction meter. On subnets created before its introduction, the instruction total
counts only from when their replicas started tracking it. The API is EXPERIMENTAL.
* New definitions of cost schedules and nominal cycles.
* Clarified semantics of the subnet metrics at `/subnet/<subnet_id>/metrics` in the
certified state tree. `num_canisters` and `canister_state_bytes` are current values, not
counters. `canister_state_bytes` is refreshed only every 10 blocks. `consumed_cycles_total`
counts nominal cycles and can decrease. `update_transactions_total` counts the messages
executed in replicated mode.

### 0.68.0 (2026-09-14) {$0_68_0}
* New management canister method `flexible_http_request`, a variant of `http_request` in which a committee
of nodes return their individual HTTP responses to the caller instead of the subnet reaching consensus
Expand Down
35 changes: 31 additions & 4 deletions docs/references/ic-interface-spec/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,23 @@ Once the IC frees the resources of a canister, its id, *cycle* balances, *contro

:::

#### Cost schedules {#cost-schedules}

A subnet's *cost schedule* determines how the protocol charges canisters for resource use, such as instruction execution, memory usage, and HTTPS outcalls. There are two schedules:

- **Normal:** applies the protocol's ordinary resource charges in cycles.
- **Free:** waives these resource charges, so they do not deduct cycles from canister balances.

The cost schedule affects resource charges only. Cycles that a canister burns explicitly using [`ic0.cycles_burn128`](./canister-interface.md#system-api-cycles) are deducted from its balance under either schedule. The remaining cycle balances of a deleted canister are likewise lost under either schedule. So are the cycles of a dropped message or refund, for example one whose recipient no longer exists.

#### Nominal cycles {#nominal-cycles}

*Nominal cycles* are accounting quantities used to measure consumption in cycle metrics.

For resource charges, the nominal amount is the amount calculated under the [normal cost schedule](#cost-schedules). Under that schedule, the nominal charge equals the cycles actually charged. Under a free cost schedule, resource use such as instruction execution or memory usage still records the nominal charge in metrics, while the actual charge to canister balances is zero. Nominal consumption therefore measures the accounted cost even when no cycles are deducted from a canister's balance. It must not be interpreted as the number of cycles actually removed from circulation.

For cycles that are lost under either schedule, such as cycles burned explicitly, the nominal amount equals the amount lost.

#### Canister status {#canister-status}

The canister status can be used to control whether the canister is processing calls:
Expand Down Expand Up @@ -517,10 +534,20 @@ The state tree contains information about the topology of the Internet Computer.

A collection of subnet-wide metrics related to this subnet's current resource usage and/or performance. The metrics are a CBOR map with the following fields:

- `num_canisters` (`nat`): The number of canisters on this subnet.
- `canister_state_bytes` (`nat`): The total size of the state in bytes taken by canisters on this subnet since this subnet was created.
- `consumed_cycles_total` (`map`): The total number of cycles consumed by all current and deleted canisters on this subnet. It's a map of two values, a low part of type `nat` and a high part of type `opt nat`.
- `update_transactions_total` (`nat`): The total number of transactions processed on this subnet since this subnet was created.
- `num_canisters` (`nat`): The number of canisters on this subnet. This is a current value, not a counter, so it decreases when canisters are deleted.

- `canister_state_bytes` (`nat`): The total size of the state in bytes currently taken by canisters on this subnet. This is a current value, not a counter. Recomputing it is expensive, so it is refreshed only every 10 blocks, at heights that are multiples of 10. It reads 0 until the first refresh after this subnet was created.

- `consumed_cycles_total` (`map`): The total [nominal cycles](#nominal-cycles) accounted for by this subnet. This is the sum of:

- the historical consumption of the canisters currently on this subnet;
- the historical consumption of the canisters deleted on this subnet, plus their remaining cycle balances at deletion;
- the cycles charged for HTTPS outcalls, threshold signature requests, and vetKD requests, which are accounted for at the subnet level rather than per canister;
- 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 preserves canister histories and redistributes them with the canisters. The original subnet thus loses their contribution. The new subnet inherits consumption from before its creation. The total can therefore decrease and is not limited to consumption that occurred on this subnet. It's a map of two values, a low part of type `nat` and a high part of type `opt nat`.

- `update_transactions_total` (`nat`): The total number of transactions processed on this subnet, i.e., the total number of messages executed in the replicated mode. It covers the whole lifetime of the subnet, or the period since the metric was introduced for subnets that predate it. The value is monotonically non-decreasing.
Comment thread
mraszyk marked this conversation as resolved.


:::note
Expand Down
45 changes: 45 additions & 0 deletions docs/references/ic-interface-spec/management-canister.md
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,51 @@ A single metric entry is a record with the following fields:

- `num_block_failures_total` (`nat64`): the number of failed block proposals by this node.

### IC method `subnet_metrics` {#ic-subnet_metrics}

This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages.

:::note

The subnet metrics management canister API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way.

:::

Given a subnet ID as input, this method returns a record of subnet-wide metrics describing that subnet's resource usage and performance.

The fields `num_canisters`, `canister_state_bytes`, `consumed_cycles_total`, and `update_transactions_total` report the same quantities that the certified state tree exposes at the path `/subnet/<subnet_id>/metrics` (see [Subnet information](./index.md#state-tree-subnet)). This method makes them available to canisters, which cannot read the state tree. The fields `block_height` and `million_round_instructions_total` have no path in the state tree and are only available through this method.

In the following, *the subnet* refers to the subnet identified by the `subnet_id` argument.

Only `block_height` describes the block in whose execution the call is processed. The other five fields are aggregates that the subnet refreshes at block boundaries, so they describe the subnet as of an earlier block. They are not all refreshed at the same rate, so they need not be mutually consistent. None of them should be read as a snapshot taken at `block_height`.

The fields returned are:

- `block_height` (`nat`): the current block height of the subnet, i.e., the height of the block in whose execution this call is processed.

Heights are consecutive numbers identifying the successive blocks of a subnet. This specification does not otherwise model block heights, and heights of different subnets are unrelated, so this value is only meaningful when compared against other values for the same subnet.

The value is monotonically non-decreasing for a given subnet.

- `num_canisters` (`nat`): the number of canisters on the subnet. This is a current value, not a counter, so it decreases when canisters are deleted.

- `canister_state_bytes` (`nat`): the total size in bytes of the state taken by canisters on the subnet. This is a current value, not a counter. Recomputing it is expensive, so it is refreshed only every 10 blocks, at heights that are multiples of 10. It can be up to 10 blocks behind `block_height` and up to 9 blocks behind the other aggregates. It reads 0 until the first refresh after the subnet was created.

- `consumed_cycles_total` (`nat`): the total [nominal cycles](./index.md#nominal-cycles) accounted for by the subnet. This is the sum of:

- 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 for HTTPS outcalls, threshold signature requests, and vetKD requests, which are accounted for at the subnet level rather than per canister;
- 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 preserves canister histories and redistributes them with the canisters. The original subnet thus loses their contribution. The new subnet inherits consumption from before its creation. The total can therefore decrease and is not limited to consumption that occurred on the subnet.

- `update_transactions_total` (`nat`): the total number of transactions processed on the subnet, i.e., the total number of messages executed in the replicated mode. The value is monotonically non-decreasing for a given subnet.

- `million_round_instructions_total` (`nat`): the total number of instructions the subnet accounted for when executing its blocks, in units of one million and rounded up. For example, a value of `42` represents an underlying count from 41,000,001 through 42,000,000 instructions. Besides the executed Wasm instructions, it covers the fixed per-execution and per-canister overheads charged by the scheduler. It also covers the charges for work performed outside of Wasm execution, such as compilation, chunk assembly, and snapshot operations. It is therefore not a Wasm instruction meter. The value is monotonically non-decreasing for a given subnet.

`update_transactions_total` and `million_round_instructions_total` cover the whole lifetime of the subnet, or the period since the respective metric was introduced for subnets that predate it.

### IC method `subnet_info` {#ic-subnet_info}

This method can only be called by canisters, i.e., it cannot be called by external users via ingress messages.
Expand Down
19 changes: 19 additions & 0 deletions docs/references/management-canister.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,25 @@ Returns a time series of node metrics for a given subnet. Returns up to 60 times
- `num_blocks_proposed_total` (`nat64`)
- `num_block_failures_total` (`nat64`)

### `subnet_metrics`

> This API is **experimental** and may change in a non-backward-compatible way.

Returns subnet-wide metrics for a given subnet, which does not have to be the subnet hosting the caller. `num_canisters`, `canister_state_bytes`, `consumed_cycles_total`, and `update_transactions_total` report the same quantities that the certified state tree exposes at `/subnet/<subnet_id>/metrics`. This method makes them available to canisters, which cannot read the state tree. `block_height` and `million_round_instructions_total` have no path in the state tree and are only available here. The full field semantics are in the [interface specification](ic-interface-spec/management-canister.md#ic-subnet_metrics).

- **Caller:** Canisters only
- **Parameters:**
- `subnet_id` (`principal`): any subnet
- **Returns:**
- `block_height` (`nat`): the height of the block in whose execution the call is processed
- `num_canisters` (`nat`): number of canisters on the subnet
- `canister_state_bytes` (`nat`): total size of canister state in bytes
- `consumed_cycles_total` (`nat`): total [nominal cycles](ic-interface-spec/index.md#nominal-cycles) accounted for by the subnet
- `update_transactions_total` (`nat`): total messages executed in replicated mode on the subnet
- `million_round_instructions_total` (`nat`): total instructions the subnet accounted for when executing its blocks, in units of one million and rounded up

Only `block_height` is as of the block that processes the call. The other five fields are aggregates refreshed at block boundaries, so they describe an earlier block. They are not refreshed in lockstep with each other. `canister_state_bytes` is recomputed only every 10 blocks. It reads 0 until the first recomputation after the subnet was created. `consumed_cycles_total` can decrease (refunds, subnet splitting) and is not a count of cycles burned. `update_transactions_total` and `million_round_instructions_total` never decrease.

### `subnet_info`

Returns metadata about a subnet.
Expand Down
49 changes: 49 additions & 0 deletions public/references/ic.did
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,54 @@ type node_metrics_history_result = vec record {
node_metrics : vec node_metrics;
};

type subnet_metrics_args = record {
subnet_id : principal;
};

// Only `block_height` describes the block in whose execution the call is
// processed. The other five fields are aggregates refreshed at block
// boundaries, so they describe the subnet as of an earlier block, and they are
// not all refreshed at the same rate; see the individual fields.
type subnet_metrics_result = record {
// Current block height of the subnet, i.e. the height of the block in
// whose execution this call is processed. Monotonically non-decreasing for
// a given subnet; the heights of different subnets are unrelated.
block_height : nat;
// Number of canisters on the subnet. A current value, not a counter.
num_canisters : nat;
// Total size in bytes of the state taken by canisters on the subnet. A
// current value, not a counter. Recomputing it is expensive, so it is
// refreshed only every 10 blocks, at heights that are multiples of 10. It
// can be up to 10 blocks behind block_height and up to 9 blocks behind the
// other aggregates. Reads 0 until the first refresh after subnet creation.
canister_state_bytes : nat;
// Total nominal cycles accounted for by the subnet: the historical
// consumption of current and deleted canisters (plus the remaining balances
// of deleted canisters), the cycles charged for HTTPS outcalls, threshold
// signature requests, and vetKD requests (accounted for at the subnet level
// rather than per canister), and the cycles lost when messages or refunds
// are dropped (e.g. because their recipient no longer exists). Resource use
// is recorded at normal cost-schedule rates even where it is free, so this
// is not a count of cycles burned. 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.
consumed_cycles_total : nat;
// Total number of transactions processed on the subnet, i.e. the total
// number of messages executed in the replicated mode. Monotonically
// non-decreasing.
update_transactions_total : nat;
// Total instructions the subnet accounted for when executing its blocks,
// in units of one million and rounded up: a value of 42 represents
// 41,000,001 through 42,000,000 instructions. 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, so it is not a Wasm instruction meter.
// Monotonically non-decreasing. Like `block_height`, it has no counterpart
// under `/subnet/<subnet_id>/metrics` in the certified state tree.
million_round_instructions_total : nat;
};

type subnet_info_args = record {
subnet_id : principal;
};
Expand Down Expand Up @@ -762,6 +810,7 @@ service ic : {

// metrics interface
node_metrics_history : (node_metrics_history_args) -> (node_metrics_history_result);
subnet_metrics : (subnet_metrics_args) -> (subnet_metrics_result);

// subnet info
subnet_info : (subnet_info_args) -> (subnet_info_result);
Expand Down
Loading