Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 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,44 @@ 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
14 changes: 14 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,20 @@ 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 cycles consumed, the total number of processed transactions, and
the total number of instructions accounted for across all rounds
(`million_round_instructions_total`), in units of one million and rounded up. The four
aggregates were previously only readable by external users via the certified state tree
path `/subnet/<subnet_id>/metrics`; the block height and the instruction total have no
path there. Besides the executed Wasm instructions, the instruction total 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, and
its counter starts when a subnet's replica begins tracking it rather than at subnet
creation. The API is EXPERIMENTAL.

### 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
25 changes: 21 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 and memory usage. 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 has no impact on explicit cycle burning: they are still burnt from canister balances.

#### 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.

Hence, nominal consumption metrics must not be interpreted as the number of cycles actually removed from circulation.

#### 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,10 @@ 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, and reads 0 until the first refresh after this subnet was created.
- `consumed_cycles_total` (`map`): The total [nominal cycles](#nominal-cycles) accounted for by the subnet. This sums the historical consumption of canisters currently on the subnet and the subnet's retained accounting for deleted canisters (including their remaining balances at deletion) and consumption on behalf of the subnet itself. Refunds of prepaid charges reduce the total. Subnet splitting preserves canister histories and redistributes them with the canisters, so the original subnet loses their contribution and 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 since this subnet was created, i.e., the total number of messages executed in the replicated mode. The value is monotonically non-decreasing.


:::note
Expand Down
38 changes: 38 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,44 @@ 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, and 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 sums the historical consumption of canisters currently on the subnet and the subnet's retained accounting for deleted canisters (including their remaining balances at deletion) and consumption on behalf of the subnet itself. Refunds of prepaid charges reduce the total. Subnet splitting preserves canister histories and redistributes them with the canisters, so the original subnet loses their contribution and 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.

- `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 across the execution phases of all rounds, in units of one million and rounded up, so a value of `42` represents an underlying count from 41,000,001 through 42,000,000 instructions. Besides the executed Wasm instructions this also covers the fixed per-execution and per-canister overheads charged by the scheduler, and 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
23 changes: 23 additions & 0 deletions docs/references/management-canister.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,29 @@ 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. The four aggregates (`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.

- **Caller:** Canisters only
- **Parameters:**
- `subnet_id` (`principal`): any subnet
- **Returns:**
- `block_height` (`nat`): the target subnet's current block height, i.e. the height of the block in whose execution the call is processed
- `num_canisters` (`nat`): 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 transactions processed on the subnet
- `million_round_instructions_total` (`nat`): total instructions the subnet accounted for across the execution phases of all rounds, 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, and they are not refreshed in lockstep with each other. `canister_state_bytes` is the stalest: it is recomputed only every 10 blocks, at heights that are multiples of 10, so it can be up to 10 blocks behind `block_height` and up to 9 blocks behind the other aggregates, and it reads 0 until the first recomputation after the subnet was created.

`update_transactions_total` and `million_round_instructions_total` only ever grow. `consumed_cycles_total` sums the historical nominal consumption of current canisters and the subnet's retained accounting for deleted canisters (including their remaining balances at deletion) and consumption on behalf of the subnet itself. Nominal charges can increase this metric under a free cost schedule without deducting cycles from canister balances. Refunds reduce it, and subnet splitting redistributes canisters' historical contributions, so the total can decrease and can include consumption from before the receiving subnet was created. `num_canisters` and `canister_state_bytes` are current values, not counters.

`million_round_instructions_total` counts the executed Wasm instructions plus the scheduler's per-execution and per-canister overheads and the charges for work outside Wasm execution (compilation, chunk assembly, snapshots), so it is not a Wasm instruction meter. A reported value of `42` represents an underlying count from 41,000,001 through 42,000,000 instructions. Both instruction and transaction counters cover the subnet's whole lifetime, or the period since each metric was introduced for subnets that predate it.

### `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;
// Nominal cycles record consumption at normal cost-schedule rates even
// when resource use is free; they are not a spendable balance.
// Total nominal cycles accounted for by the subnet: current canisters'
// historical consumption plus the subnet's retained accounting for deleted
// canisters (including their remaining balances at deletion) and consumption
// on behalf of the subnet itself. Free cost schedules can produce nominal
// charges without deducting cycles from canister balances. Refunds 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 limited to consumption on this subnet.
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 across the execution phases
// of all rounds, 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.
// Unlike the four fields above, 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