diff --git a/docs/references/ic-interface-spec/abstract-behavior.md b/docs/references/ic-interface-spec/abstract-behavior.md index 6ecb9f63..a599cb75 100644 --- a/docs/references/ic-interface-spec/abstract-behavior.md +++ b/docs/references/ic-interface-spec/abstract-behavior.md @@ -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 = +``` + +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. diff --git a/docs/references/ic-interface-spec/changelog.md b/docs/references/ic-interface-spec/changelog.md index a225f33a..7a85ae83 100644 --- a/docs/references/ic-interface-spec/changelog.md +++ b/docs/references/ic-interface-spec/changelog.md @@ -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//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//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 diff --git a/docs/references/ic-interface-spec/index.md b/docs/references/ic-interface-spec/index.md index 5a2b0935..20cfdc95 100644 --- a/docs/references/ic-interface-spec/index.md +++ b/docs/references/ic-interface-spec/index.md @@ -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: @@ -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. :::note diff --git a/docs/references/ic-interface-spec/management-canister.md b/docs/references/ic-interface-spec/management-canister.md index e3723758..1aa238f4 100644 --- a/docs/references/ic-interface-spec/management-canister.md +++ b/docs/references/ic-interface-spec/management-canister.md @@ -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//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. diff --git a/docs/references/management-canister.md b/docs/references/management-canister.md index 720826cf..37d87a1b 100644 --- a/docs/references/management-canister.md +++ b/docs/references/management-canister.md @@ -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//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. diff --git a/public/references/ic.did b/public/references/ic.did index 8918b2fc..8000333a 100644 --- a/public/references/ic.did +++ b/public/references/ic.did @@ -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//metrics` in the certified state tree. + million_round_instructions_total : nat; +}; + type subnet_info_args = record { subnet_id : principal; }; @@ -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);